列の値に基づいて重複行を削除する

列の値に基づいて重複行を削除する

約 25 GB のテキスト ファイルがあります。2 列目の値に基づいて重複行を削除したいと考えています。ファイル内に重複が見つかった場合は、その列の値を持つすべての行を削除し、4 列目の最高値を持つ 1 行のみを残します。ファイルは CSV 形式で、すでに並べ替えられています。

storm_id,Cell_id,Windspeed,Storm_Surge,-1
2,10482422,45,0.06,-1
2,10482422,45,0.18,-1
2,10482422,45,0.4,-1
2,10482423,45,0.15,-1
2,10482423,45,0.43,-1
2,10482424,45,0.18,-1
2,10482424,45,0.49,-1
2,10482425,45,0.21,-1
2,10482425,45,0.52,-1
2,10482426,45,0.27,-1
2,10482426,45,0.64,-1
2,10482427,45,0.09,-1
2,10482427,45,0.34,-1
2,10482427,45,0.73,-1

上記の例では、Cell_Id重複する行を削除して、それぞれに1つの最大サージ値を取得したいだけです。

期待される出力は次のとおりです。

2,10482422,45,0.4,-1
2,10482423,45,0.43,-1
2,10482424,45,0.49,-1
2,10482425,45,0.52,-1
2,10482426,45,0.64,-1
2,10482427,45,0.73,-1

答え1

入力はすでに2列目でグループ化/ソートされているように見えるので、これは非常に簡単で、しないデータセット全体をメモリ内に保持してソートする必要がありますが、一度に 2 つのレコードのみが必要です。1

最初は Awk によるソリューションを考えましたが、配列や空白でないフィールド区切り文字を扱うのが面倒だとわかりました。そこで、比較的短い Python プログラムにすることにしました。

#!/usr/bin/python3
import sys
DELIMITER = ','

def remove_duplicates(records):
    prev = None
    for r in records:
        r = (int(r[0]), int(r[1]), int(r[2]), float(r[3]), int(r[4]))
        if prev is None:
            prev = r
        elif r[1] != prev[1]:
            yield prev
            prev = r
        elif r[3] > prev[3]:
            prev = r
    if prev is not None:
        yield prev

def main():
    for r in remove_duplicates(
        l.rstrip('\n').rsplit(DELIMITER) for l in sys.stdin
    ):
        print(*r, sep=',')

if __name__ == '__main__':
    main()

私のシステムでは、スループットは約 250,000 レコード、または CPU 1 秒あたり 5 MB です。

使用法

python3 remove-duplicates.py < input.txt > output.txt

プログラムは列ヘッダーを処理できないため、列ヘッダーを削除する必要があります。

tail -n +2 < input.txt | python3 remove-duplicates.py > output.txt

それらを結果に追加したい場合は、次のようにします。

{ read -r header && printf '%s\n' "$header" && python3 remove-duplicates.py; } < input.txt > output.txt

1これは、ウォルティネーターのそしてスチールドライバーの メインメモリに収まらないデータ セットに対するアプローチ。

答え2

もしあなたがそれらを分類していたら減少4番目のフィールドの順序については、連想配列またはハッシュを使用して、各2番目のフィールド値の最初の出現を取得するだけで済みますawk -F, '!seen[$2]++' fileperl -F, -ne 'print $_ unless $seen{$F[1]}++'

値が昇順になっている場合、効率的な単一パスで実行するのは少し難しくなりますが、キー値が変わるたびに前の行を印刷することで (少し設定すれば) 実行できます。

awk -F, '
  NR==1 {print; next}        # print the header line
  NR==2 {key=$2; next}       # initialize the comparison
  $2 != key {
    print lastval; key = $2  # print the last (largest) value of the previous key group
  } 
  {lastval = $0}             # save the current line
  END {print lastval}        # clean up
' file
storm_id,Cell_id,Windspeed,Storm_Surge,-1
2,10482422,45,0.4,-1
2,10482423,45,0.43,-1
2,10482424,45,0.49,-1
2,10482425,45,0.52,-1
2,10482426,45,0.64,-1
2,10482427,45,0.73,-1

答え3

一意の Cell_id があまり多くない場合は、Perl 連想配列で既に見つかったものを追跡できます。多すぎる場合 (Perl スクリプトがメモリ不足になる場合)、C一意のものをビット フィールドに保持するプログラムを作成します。Perl は次のとおりです。

#!/usr/bin/perl -w
use strict;
my %seen = ();          # key=Cell_ID, value=1
my @cols=();            # for splitting input

while( <> ) {           # read STDIN
  @cols = split ',',$_;
  next if ( defined $seen{$cols[1]}); # skip if we already saw this Cell_Id
  $seen{$cols[1]} = 1;
  print;
}

これが私のテストです:

walt@bat:~(0)$ cat u.dat
storm_id,Cell_id,Windspeed,Storm_Surge,-1
2,10482422,45,0.06,-1
2,10482422,45,0.18,-1
2,10482422,45,0.4,-1
2,10482423,45,0.15,-1
2,10482423,45,0.43,-1
2,10482424,45,0.18,-1
2,10482424,45,0.49,-1
2,10482425,45,0.21,-1
2,10482425,45,0.52,-1
2,10482426,45,0.27,-1
2,10482426,45,0.64,-1
2,10482427,45,0.09,-1
2,10482427,45,0.34,-1
2,10482427,45,0.73,-1
walt@bat:~(0)$ perl ./unique.pl u.dat
storm_id,Cell_id,Windspeed,Storm_Surge,-1
2,10482422,45,0.06,-1
2,10482423,45,0.15,-1
2,10482424,45,0.18,-1
2,10482425,45,0.21,-1
2,10482426,45,0.27,-1
2,10482427,45,0.09,-1

関連情報