CSVファイルの「;」で区切られた値のリストの最小値

CSVファイルの「;」で区切られた値のリストの最小値

一部のセルには複数の値を含む大容量CSVファイルがあります。最小値のみを含むようにこれらのセルをどのように変更できますか?

たとえば、次の入力が与えられた場合:

id,disease_1,disease_2
1001,2008;2009,2009;2010 

ノート

  1. 列/フィールド区切り記号はコンマです。,
  2. 各セルの値はセミコロンで区切られ、;昇順にソートされます。
  3. 列2でアルゴリズムを開始したいと思います。

希望の出力:

id,disease_1,disease_2
1001,2008,2009

答え1

サブコマンドでput使用される「式」は次のとおりです。putミラーmlr;構造化データ操作用に特別に設計されたツール)は、;各非フィールドから分離された値の最小値を計算しますid

for (key,value in mapexcept($*, "id")) {
    value !=~ ";" { continue }

    var minimum = "";

    for (i,number in splitnv(value, ";")) {
        minimum = min(minimum, number)
    }

    $[key] = minimum;
}

ここでは各レコードのフィールドを繰り返しますが、呼び出されるとフィールドはスキップされますid。フィールド値をセミコロンに分割して生成された数値を繰り返しながら、各フィールドの最小値を追跡します。ループが終了すると、フィールド値は見つかった最小値で上書きされます。含まれていないフィールドは;ループの開始時にスキップされます。

以下を使用して実行できます。

mlr --csv put -e script file.csv

...script上記の短いスクリプトを保存するファイル名はどこにありますか?または、次のようにコマンドラインにスペルを入力できます。

mlr --csv put 'for (k,v in mapexcept($*,"id")) { v !=~ ";" { continue } var m=""; for (i,n in splitnv(v,";")) { m=min(m,n) } $[k]=m; }' file.csv

質問のデータを考慮すると、結果は次のようになります。

id,disease_1,disease_2
1001,2008,2009

最新バージョンのMiller(バージョン6+)を使用すると、コードを短縮できます。かなり新しいsort()sumget_values()関数を使用すると、次のようになります。

mlr --csv put 'for (k,v in mapexcept($*,"id")) { $[k] = sort(get_values(splitnv(v,";")))[1] }' file.csv

各フィールドの分離値のリストから最初の値を選択します;

(ありがとうございます。スチールドライバーこの巧妙な書き換えを悟りました。 )


値がすでにソートされている場合は、はるかに簡単で効率的です。

mlr --csv put 'for (k,v in mapexcept($*,"id")) { $[k] = sub(v,";.*","") }' file.csv

;これにより、各フィールドの最初の文字が切り捨てられます。

答え2

あなた説明するセミコロンで区切られた項目は常に昇順にソートされるため、各グループの最初の項目は必要な最小値項目です。これに基づいて、残りの値を簡単に削除できます。

sed 's/;[^,]*//g' {file}

これは、データが単純なCSV形式であり、セミコロンとカンマを含む引用符付きテキスト文字列を使用しないと仮定します。この場合、このテキストベースのソリューションは機能せず、より完全なソリューションを使用する必要があります。回答使用ミラー

サンプルデータセットの出力

id,disease_1,disease_2
1001,2008,2009

答え3

単純なCSVの場合:

$ perl -MList::Util=min -F, -le 'print join ",", shift@F, map {min split /;/} @F' file.csv
id,disease_1,disease_2
1001,2008,2009

答え4

awkの場合、子供がまだ昇順にソートされていないと仮定します。

awk '
BEGIN{ FS=OFS="," }
function min(list) {
    subNums=split(list, numbr, /;/)
    min=numbr[1]
    for(n=2; n<=subNums; n++)
        if(numbr[n]<min)
            min=numbr[n]
    return min
}
{
  for(fldNr=2; fldNr<=NF; fldNr++)
      $fldNr=min($fldNr)
}' infile.csv

GNU awkを使用してください(asort()機能配列も並べ替えます。PROCINFO["sorted_in"]オプション)。

awk '
BEGIN{ FS=OFS=","; PROCINFO["sorted_in"]="@val_num_asc" }
{
  for(fldNr=2; fldNr<=NF; fldNr++)
  {
    subNums=split(fldNr, numArr, /;/)
    asort(numArr)
    $fldNr=numArr[1]
  }
}' infile.csv
  • BEGIN{ FS=OFS="," }入力フィールドと出力フィールドの区切り文字を「,」に設定します。

  • subNums=split(fldNr, numArr, /;/)現在のフィールドを;子区切り文字に分割し、結果の値を名前付き配列に格納します。シリアル番号

  • それから私たちはasort(numArr)並べ替えるシリアル番号値は昇順でソートされます。

  • $fldNr=numArr[1]ソート後に最初の(最小)値を割り当てます。シリアル番号現在のフィールド#の配列です。

最初の awk コマンドは同じことを行いますが、for ループを使用して子数を繰り返し、GNU awk 拡張を使用せずに各ペアを一度に 1 つずつ比較して最小値を見つけます。

ただし、各フィールドのサブ番号を昇順に並べ替えると、最初のサブ番号(最も小さい数字)を選択するだけで、コードがはるかに簡単になります。

awk '
BEGIN{ FS=OFS="," }
{
  for(fldNr=2; fldNr<=NF; fldNr++)
  {
    split($fldNr, numbr, /;/)
    $fldNr=numbr[1]
    continue
  }
}' infile.csv

関連情報