여러 행과 열을 기반으로 그룹화

여러 행과 열을 기반으로 그룹화

데이터 예시는 아래와 같습니다. 처음 두 열은 ID이고 세 번째 열은 빈도입니다.

1 2 99
2 3 62
4 5 80
4 4 98
5 5 79
6 1 98

첫 번째와 두 번째 열은 동일인이거나 중복된 ID입니다. 예를 들어 1, 2, 3, 6은 같은 사람입니다. 1==2, 2==3 등이기 때문에 1==3입니다. 따라서 데이터를 이렇게 나눌 수 있습니다.

사람 1

1 2 99
2 3 62
6 1 98

사람 2

4 5 80
4 4 98
5 5 79

위와 같이 데이터를 어떻게 분할할 수 있나요? 여기에서 행 전체를 비교해야 합니다. 이것은 나에게 혼란스러운 부분입니다. 그런 다음 각 그룹 내에서 세 번째 열의 빈도를 기준으로 ID를 선택하고 싶습니다. 여기서는 다른 파일에서 이러한 ID를 제거하기 위해 빈도가 가장 낮은 동물을 가져옵니다. 선호되는 최종 출력은 다음과 같습니다.

2 3 62
6 1 98
4 5 80
5 5 79 

나는 답을 찾고 있지만 나에게는 복잡해 보인다. 어쩌면 데이터를 분할하는 것보다 더 좋은 방법이 있을 수도 있습니다. 어떤 아이디어라도 부탁드립니다.

답변1

첫 번째 질문을 해결하려면 모든 Unix 시스템의 bourne 파생 쉘에서 awk+sort를 사용하여 입력을 분할하는 방법은 다음과 같습니다(저는 shebang에서 bash를 사용하지만 bash일 필요는 없습니다).

$ cat tst.sh
#!/usr/bin/env bash

awk '{ print $0 ORS $2, $1, $3 }' "${@:--}" |
sort -n -k1,1 -k2,2 |
awk '
    !seen[($1 > $2 ? $1 FS $2 : $2 FS $1)]++ {
        out = ""

        for ( i=1; i<=2; i++ ) {
            if ( $i in map ) {
                out = map[$i]
                break
            }
        }

        if ( out == "" ) {
            out = "person_" (++numPeople)
        }

        for ( i=1; i<=2; i++ ) {
            map[$i] = out
        }

        print >> out
        close(out)
    }
'

다음에 설명된 줄을 포함하도록 게시한 예제 입력을 수정해야 합니다.내 댓글분할이 작동하는지 실제로 테스트하려면 다음을 수행하십시오.

$ cat file
1 2 99
2 3 62
4 5 80
4 4 98
5 5 79
6 1 98
7 8 99
9 10 98
9 7 97

$ ./tst.sh file

$ head person*
==> person_1 <==
1 2 99
1 6 98
2 3 62

==> person_2 <==
4 4 98
4 5 80
5 5 79

==> person_3 <==
7 8 99
7 9 97
9 10 98

1 2 x위의 내용은 와 동일하므로 각 행의 처음 2개 ID의 순서가 중요하지 않다고 가정합니다 2 1 x.

답변2

"분할 데이터" 부분에 대한 부분적인 답변입니다. GNU awk기능을 사용합니다.배열의 배열그래서 gawk해결책이 있습니다.

1열과 2열의 ID를 겹쳐서 그룹화하고 각 사람에게 고유한 ID를 부여한 후 다음 이름의 파일로 인쇄합니다 person_ID.

gawk '
#id=array of arrays with unique ids UID and alias
#IDs aID as taken from the file: id[UID][aID]

#manually create first entry in line 1
NR==1 { id[1][$1]=1 ; id[1][$2]=1 ; next }

#on other input: scan array id for a match in aIDs
#use related UID it if match is found
FNR==NR {
  for (i=1 ; i<=length(id) ; i++ ) {
    if ($1 in id[i] || $2 in id[i] ) {
      id[i][$1]=i ; id[i][$2]=i ; next }
  }
#if no match was found, create a new UID:
  new=length(id)+1
  id[new][$1]=new ; id[new][$2]=new
}

#rerun through id arrays to check for doubles:
FNR!=NR && !b {
  for ( i in id ) {
    for ( j in id[i] ) {
      if ( seen[j] ) {
        for ( k in id[i] ) { id[seen[j]][k]=id[seen[j]][k] }
        delete id[i]
        }
       else { seen[j]=i }
    }
  }
  delete seen
#adjust UIDs as they may be out of order now to new id array nid,
#delete old id array:
  for ( i in id ) {++n ; for (j in id[i]) { nid[n][j]=id[i][j] } }
  delete id
  b=!b
}

#write to separate files per UID
FNR!=NR {
  for (i in nid) {
    if ($1 in nid[i] || $2 in nid[i] ) { print > "person_"i }
  }
}

#This is just to print the aID vs UID map
END {
  for (i in nid) {
    print "aIDs for person UID=",i ; b=1
    for (j in nid[i]) {
      if (b) {printf j ; b=0}
      else {printf ","j}
    }
  print ""
  }
}
' infile infile

이제 줄 제거 문제에 대해 다음과 같은 매우 간단한 접근 방식을 권장합니다.

위에서 생성된 파일을 가져와서 person_i각 파일의 필드 3에서 가장 작은 값을 가진 행을 선택합니다. 다음 줄을 delete_me파일에 쓰고 원본 파일에서 역방향 grep을 사용합니다.

for file in person_* ; do
  sort -n -k3 ${file} | head -n1
done > delete_me
grep -xvf delete_me original

sort동일하거나 유사한 번호일 경우에는 최소한의 선택만으로 이루어지기 때문에 별도의 정제작업은 이루어지지 않습니다. 일치 항목이 전체 줄을 완전히 포함해야 하는지 확인하려면 -xfor를 사용합니다 (그렇지 않으면 와 일치합니다 ).grep1 2 31 2 31 2 33


다음 변형은 ID별로만 사람을 그룹화하고 열 3에서 최대값이 있는 행을 필터링하여 고유한 사람을 찾습니다. 입력 파일을 두 번째로 읽을 때 최대 줄이 없는 줄만 인쇄됩니다. 따라서 추가 파일이 없는 단일 스크립트 솔루션은 다음과 같습니다.

#id=array of arrays with unique ids UID and alias
#IDs aID as taken from the file: id[UID][aID]

#manually create first entry in line 1, print to "person_1"
NR==1 { id[1][$1]=1 ; id[1][$2]=1 ; next }
#manually create first entry for line deletion selection
NR==1 { max[1]=$3 ; line[1]=$0 }

#on other input: scan array id for a match in aIDs
#use related UID it if match is found
FNR==NR {
  for (i=1 ; i<=length(id) ; i++ ) {
    if ($1 in id[i] || $2 in id[i] ) {
      id[i][$1]=i ; id[i][$2]=i
#select line for deletion
      if ($3>=max[i]) { max[i]=$3 ; line[i]=$0 }
      ; next
    }
  }
#if no match was found, create a new UID:
  new=length(id)+1
  id[new][$1]=new ; id[new][$2]=new
  max[new]=$3 ; line[new]=$0
}

#rerun through id arrays to check for doubles:
FNR!=NR && !b {
  for ( i in id ) {
    for ( j in id[i] ) {
      if ( seen[j] ) {
        for ( k in id[i] ) { id[seen[j]][k]=id[seen[j]][k] }
        delete id[i]
#adjust line deletion selection:
        if (max[seen[j]]>=max[i]) { delete line[i]} else {delete line[seen[j]]}
      }
       else { seen[j]=i }
    }
  }
  delete seen
#adjust UIDs as they may be out of order now:
  for ( i in id ) {++n ; for (j in id[i]) { nid[n][j]=id[i][j] } }
  delete id
#swap line deletion marker from array element to index for better processability
  for (i in line) { line[line[i]]="" ; delete line[i]}
  delete max
#set flag for running this block once only
  b=!b
}

#write to separate files per UID (commented out)
#FNR!=NR {
#  for (i in nid) {
#    if ($1 in nid[i] || $2 in nid[i] ) {print > "person_"i}
#  }
#}
#print lines that have not been selected for deletion
#to STDOUT use the alternative to print it to a separate file
FNR!=NR && ! ($0 in line)
#alternative:
#FNR!=NR && ! ($0 in line) {print > "myoutfile"}


#This is just to print the aID vs UID map
END { print "\n------\nUID vs aID map:\n"
  for (i in nid) {
    print "aIDs for person UID=",i ; b=1
    for (j in nid[i]) {
      if (b) {printf j ; b=0}
      else {printf ","j}
    }
  print ""
  }
}

다음으로 실행합니다 awk -f script.awk infile infile. 즉, infile을 두 번 읽습니다.

관련 정보