列内の負の値の発生回数を計算し、関連する行名をリストするAwkスクリプト

列内の負の値の発生回数を計算し、関連する行名をリストするAwkスクリプト

列に負の値を持つ国のリストを生成して計算するawkスクリプトを作成しようとしています。

サンプル:

Country, COL2,COL3,COL4,COL5
Poland,  -0.3, 0,   2,  -0.5
Canada,  -1,   1,   1,  -0.4
Italy,    7,  -5,   3,  -0.1
France,   1,   2,  -0.5, 7 
Portugal, 1,   NULL,   4,   1

希望の出力:

2 COL2, Poland, Canada, 
1 COL3, Italy, 
1 COL4, France, 
3 COL5, Poland, Canada, Italy, 

スクリプトの作成を開始しましたが、期待した結果に近いものではありません。

#!/usr/bin/bash 

INPUT=./happiness2.csv
OLDIFS=$IFS
IFS=','

awk 'NF==1{next} 
  {country=$1; $1=""; gsub(/[^-]/,"",$0); l=length($0); 
   print country, l;
        }
  }' < $INPUT

誰でも助けることができますか?

答え1

ハードコーディングではなく、最初の行から列名を読みます。最初の行の余分なスペースを削除できる場合は、出力をよりきれいにするのに役立ちます。

編集する:

#!/usr/bin/awk -f
# The arrays are
# name, indexed by column number, the names of the columns taken from the first line.
# cl, indexed by the column name, the list of countries for which
#    this column is negative.
# cnt, indexed by column name, the count of the number of countries.
BEGIN { FS="," }
NR==1 { for(i=2;i<=NF;i++) { name[i]=$i } ; next }
{
    # loop over the columns
    for(i=2;i<=NF;i++) {
        # get the value of the column as a number
        v=$i+0
        # move on to the next column if the value is non negative.
        if (v>=0) continue;
        # get the name of the column
        n=name[i]
        # increment the count and add the country onto the list
        cnt[n]++
        cl[n] = cl[n]  $1  ", "
    }
}
END { # At the end, loop over the results.
      for (i in name) {
        # get the column name
        n=name[i]
        # print out the saved data
        printf("%d %s, %s\n",cnt[n]+0, n, cl[n]); }}

出力順序は明確に定義されていません。

一般に、誰かが説明を求めると、それを提供するのが役立ちます。

答え2

まず、以下を使用してコンマの周りのすべてのスペースを削除します(たとえば、スペースを含むヘッダーフィールドがありますが、質問に提供されているデータに十分な場合は、sedこの方法をより慎重に使用できます)。csvformat -Sパイプラインは、転置されたデータを使用して、各行datamashに負の値を持つ国を出力します。

#!/bin/sh

sed 's/ *, */,/g' file |
datamash -t, transpose |
awk -F, '
    BEGIN { OFS = FS }
    NR == 1 { for (i = 2; i <= NF; ++i) h[i] = $i; next }
    {
        nf = split($0,a)
        $0 = a[1]

        for (i = 2; i <= nf; ++i)
            if (a[i] < 0) $(NF+1) = h[i]

        if (NF > 1) print NF-1, $0
    }'

NR == 1コードのブロックはawkの最初の入力行に対してのみ実行されますdatamash。の出力はdatamash次のとおりです。

Country,Poland,Canada,Italy,France,Portugal
COL2,-0.3,-1,7,1,1
COL3,0,1,-5,2,NULL
COL4,2,1,3,-0.5,4
COL5,-0.5,-0.4,-0.1,7,1

これは、配列にh最初の行のヘッダーが含まれることを意味します。

の他のすべての入力行に対して、対応する数値が負のdatamash国で構成される出力レコードを生成します。これを行うには、入力行をコンマで区切って配列にし、文字列の1つでaある$0現在のレコードをリセットします。次に、他の項目を繰り返して、ゼロより小さい数字が見つかるたびに現在のレコードのヘッダーを追加します。a[1]COLah

次に、現在のレコードのフィールド数(文字列を表すために1減算COL)とレコード自体を印刷します。

質問のデータ出力を提供します。

2,COL2,Poland,Canada
1,COL3,Italy
1,COL4,France
3,COL5,Poland,Canada,Italy

print最後のものを次のように変更できます。

printf "%d\t%s\n", NF-1, $0

...最初の列を別の列とタブに分割するには、次の手順を実行します。

2       COL2,Poland,Canada
1       COL3,Italy
1       COL4,France
3       COL5,Poland,Canada,Italy

次のように入力すると、

COUNTRY NAME, SOCIAL SUPPORT, FREEDOM TO MAKE LIFE CHOICES, GENEROSITY, PERCEPTIONS OF CORRUPTION, POSITIVE AFFECT, NEGATIVE AFFECT, CONFIDENCE IN NATIONAL GOVERNMENT, DEMOCRATIC QUALITY, DELIVERY QUALITY
Afghanistan, 0.49, NULL, -0.11, 0.95, 0.49, 0.37, -0.26, -1.88, -1.43
Albania, 0.63, NULL, -0.03, 0.87, 0.66, 0.33, -0.45, 0.29, -0.13
Algeria, 0.80, NULL, -0.19, 0.69, 0.64, 0.34, 0.24, -0.92, -0.81
Argentina, 0.90, NULL, -0.18, 0.84, 0.80, 0.29, 0.30, 0.35, 0.15

...スクリプトが生成されました

4,GENEROSITY,Afghanistan,Albania,Algeria,Argentina
2,CONFIDENCE IN NATIONAL GOVERNMENT,Afghanistan,Albania
2,DEMOCRATIC QUALITY,Afghanistan,Algeria
3,DELIVERY QUALITY,Afghanistan,Albania,Algeria

答え3

Icarusの解決策に従いますが、ここでは列が昇順に印刷されます。

awk -F ' *, *' '
NR==1{split($0,a);next}
{
  for (i=2; i<=NF; i++)
    if ( $i ~ /^-/ ) {
      col = a[i]
      buf[col] = buf[col] $1 "," OFS
      knt[col]++
    }
}
END {
  for (i=2; i in a; i++) {
    col = a[i]
    if ( knt[col] )
      print knt[col], col",", buf[col]
  }
}
' file

2 COL2, Poland, Canada, 
1 COL3, Italy, 
1 COL4, France, 
3 COL5, Poland, Canada, Italy, 

関連情報