여러 열에 문자열이 n번 나타나는 행 수 계산

여러 열에 문자열이 n번 나타나는 행 수 계산

저는 탭으로 구분된 5개의 열로 구성된 수백 개의 텍스트 파일을 가지고 있습니다. 첫 번째 열에는 인덱스가 포함되고 다음 4개 열에는 발생 횟수가 포함됩니다. 이제 값이 0인 3개 열(즉, 아래 예에서는 7개 행)을 포함하는 행 수를 계산하려고 합니다.

1   0   0   0   9
2   0   9   0   0
3   10  0   0   0
4   0   10  4   0
5   0   0   0   10
6   0   0   0   10
7   0   0   0   10
8   0   10  0   0
9   5   0   5   0

이것을 R에서 루프로 코딩할 수 있지만 원본 파일에는 각각 6천만 개 이상의 라인이 포함되어 있으므로 awk 또는 sed 및 wc -l을 사용하여 이를 해결할 수 있는 방법이 없는지 궁금합니다.

답변1

예, 다음에서 할 수 있습니다 awk.

awk '{ 
       k=0; 
       for(i=2;i<=NF;i++){ 
         if($i == 0){
             k++
         }
       }
       if(k==3){
         tot++
       }
      }
      END{
          print tot
      }' file 

또한 (GNU) sedwc:

$ sed -nE '/\b0\b.*\b0\b.*\b0\b/p' file | wc -l
7

그러나 개인적으로는 Perl을 대신 사용하겠습니다.

$ perl -ale '$tot++ if (grep{$_ == 0 } @F) == 3 }{ print $tot' file 
7

또는 약간 덜 압축되었습니다.

$ perl -ale 'if( (grep{$_ == 0 } @F) == 3 ){
                  $tot++ 
              }
              END{
                  print $tot
              }' file 
7

그리고 여러분 중 골퍼들을 위해:

$ perl -ale '(grep{$_==0}@F)==3&&$t++}{print$t' file
7

설명하다

  • -ale: -aPerl을 awk처럼 동작하게 만듭니다. 입력 파일의 각 줄을 읽고 이를 공백으로 나누어 배열로 만듭니다 @F. 각 호출 에 대한 입력의 후행 줄 바꿈을 -l추가 및 제거하며 각 입력 줄에 적용해야 하는 스크립트입니다.\nprint-e
  • $tot++ if (grep{$_ == 0 } @F) == 3: $tot정확히 3개의 필드가 있을 때마다 1씩 증가합니다 0. 첫 번째 필드는 1에서 시작하므로 0이 될 수 없다는 것을 알고 있으므로 제외할 필요가 없습니다.
  • }{END{}: 이는 파일이 처리된 후 실행될 코드 블록을 제공하는 간단한 방법일 뿐입니다 . 따라서 }{ print $tot값이 있는 세 개의 필드를 포함하는 총 행 수가 인쇄됩니다 0.

답변2

그리고 GNU grep또는립그렙

$ LC_ALL=C grep -c $'\t''0\b.*\b0\b.*\b0\b' ip.txt 
7

$ rg -c '\t0\b.*\b0\b.*\b0\b' ip.txt
7

where 은 $'\t'탭 문자와 일치하므로 첫 번째 열이 0.


대용량 파일을 사용하여 예제를 실행합니다.

$ perl -0777 -ne 'print $_ x 1000000' ip.txt > f1
$ du -h f1
92M f1

$ time LC_ALL=C grep -c $'\t''0\b.*\b0\b.*\b0\b' f1 > f2
real    0m0.416s

$ time rg -c '\t0\b.*\b0\b.*\b0\b' f1 > f3  
real    0m1.271s

$ time LC_ALL=C awk 'gsub(/\t0/,"")==3{c++} END{print c+0}' f1 > f4
real    0m8.645s

$ time perl -ale '$tot++ if (grep{$_ == 0 } @F) == 3 }{ print $tot' f1 > f5
real    0m14.349s

$ time LC_ALL=C sed -n 's/\t0\>//4;t;s//&/3p' f1 | wc -l > f6
real    0m14.075s
$ time LC_ALL=C sed -n 's/\t0\>/&/3p' f1 | wc -l > f8    
real    0m6.772s

$ time LC_ALL=C awk '{ 
       k=0; 
       for(i=2;i<=NF;i++){ 
         if($i == 0){
             k++
         }
       }
       if(k==3){
         tot++
       }
      }
      END{
          print tot
      }' f1 > f7 
real    0m10.675s

LC_ALL=C파일에 ASCII가 아닌 문자가 포함될 수 있으면 삭제하십시오. 일반적 으로 테스트 실행 시보 ripgrep다 빠릅니다 . 저자 에 따르면 이는 유니코드 단어 경계를 피하기 위해 사용될 수 있지만 이는 위의 상황과 비슷한 결과를 낳습니다.GNU grepGNU grepripgrep(?-u:\b)

답변3

GNU sed 사용:

sed -E 's/\t0\>/&/3;t;d' file  | wc -l

Isaac이 지적했듯이, 정확히 3을 계산하려면 다음과 같이 할 수 있습니다.

sed -n 's/\t0\>//4;t;s//&/3p' file | wc -l

답변4

Perl을 사용하여 왼쪽의 TAB과 오른쪽의 단어 경계로 둘러싸인 0이 있는 줄 수를 총 3회 계산합니다. 마지막으로 이 줄의 줄 번호를 인쇄합니다.

perl -lne '$c += 3 == (() = /\t0\b/g)}{print $c' file
7

또 다른 방법은 필드를 살펴보는 것입니다.

perl -F'\t' -lane '$c++ if 3 == grep ! $_, @F[1..$#F]}{print $c' file

s///또 다른 방법은 스칼라 컨텍스트에서 명령을 사용하는 것입니다.

perl -lne '$c += s/\t0\b//g == 3}{print $c' file 

우리는 이를 위해 Gnu awk를 사용합니다:

awk -F'\t' '
  {
      gsub(FS, FS FS)
      $0 = $0 FS
      if ($0 != gensub(FS"0"FS, "", 3, $0))  ++c
  }
  END{print c}
' file

Gnu grep도 도움이 될 수 있습니다.

grep -cP '(.*\t0\b.*){3}' file

관련 정보