Подсчет уникальных значений в одном столбце на основе дубликатов в другом столбце

У меня есть файл с идентификатором и значением:

ABC123 111111
ABC123 111111
ABCDEF 333333
ABCDEF 111111
CCCCCC 333333
ABC123 222222
DEF123 444444
DEF123 444444

Оба столбца содержат повторяющиеся значения, но мне нужно посчитать строки с одинаковым идентификатором (первый столбец) и уникальным значением (второй столбец). Это приведет к выводу из приведенного выше ввода:

ABCDEF 2
ABC123 2
DEF123 1
CCCCCC 1

...где первый столбец — это идентификатор, а второй столбец — количество уникальных значений во втором столбце. Другими словами, мне нужно узнать, сколько уникальных значений существует для данного идентификатора.

Ближе всего я подошел к этому, но все, что он делает, это подсчитывает уникальные значения первого столбца:

cut -d " " -f1 "file.txt" | uniq -cd | sort -nr | head

Как бы мне сделать что-то подобное в Bash?

🤔 А знаете ли вы, что...
Bash позволяет использовать условные операторы, циклы и функции в скриптах.


2
70
5

Ответы:

Это awk должно сработать для вас:

awk '{
   ++c1[$1]               # frequency of 1st column
   uq[$0]                 # counts of full record
}
END {
   for (i in uq) {        # store frequency of uniques in fq
      sub(/ .*/, "", i)
      ++fq[i]
   }
   for (i in fq)          # print output from fq
      if (c1[i] > 1)
         print i, fq[i]
}' file

ABCDEF 2
DEF123 1
ABC123 2

Решено

Это достаточно близко?

$ sort -u file.txt | cut -d' ' -f1 | uniq -c
   2 ABC123
   2 ABCDEF
   1 CCCCCC
   1 DEF123

Вы можете дополнительно отфильтровать его с помощью | grep -vw '1', чтобы имитировать семантику HAVING COUNT(DISTINCT value) > 1 и исключить последние две строки из вывода в этом примере (при условии, что 1 не является допустимым значением идентификатора!).

И вы, конечно, можете изменить порядок столбцов несколькими способами. Например.

$ sort -u file.txt  |        # sort and eliminate multiple occurrences of the same '<identifier> <value>' pair
    cut -d' ' -f1   |        # keep only the identifier
    uniq -c         |        # collapse and count occurrences of the same identifier
    grep -vw '1'    |        # eliminate rows containing the word '1', assuming this can only be a count value, never an identifier!
    awk '{print $2 " " $1}'  # reverse column order to show '<identifier> <count>'
ABC123 2
ABCDEF 2

С GNU awk (для многомерных массивов):

awk '
    !seen[$1][$2]++ {++uniqs[$1]}
    END {for(id in uniqs) print id, uniqs[id]}
' file.txt
DEF123 1
ABC123 2
ABCDEF 2
CCCCCC 1

Предположения:

  • каждая строка имеет две строки, разделенные пробелами
  • повторяющиеся строки действительно являются дубликатами (например, они имеют одинаковое количество начальных, встроенных и конечных пробелов)

Еще один awk подход:

awk '
    { lines[$0] }                            # capture unique lines
END { for (line in lines) {                  # loop through list of unique lines
          split(line,a)                      # split line on white space
          counts[a[1]]++                     # count number of times we see the first field (aka "id")
      }
      for (id in counts)                     # loop through list of id
          print id, counts[id]               # print id and count
    }
' file.txt

Это генерирует:

ABC123 2
DEF123 1
ABCDEF 2
CCCCCC 1

Если выходные данные необходимо упорядочить, передайте результаты соответствующей команде sort, например:

$ awk '<see script from above>' file.txt | sort -k2,2nr -k1,1r
ABCDEF 2
ABC123 2
CCCCCC 1
DEF123 1

Вот Ruby для этого:

ruby -lane 'BEGIN{ cnt=Hash.new{|h,k| h[k]=[]} }
cnt[$F[0]]<<$F[1]
END{
    cnt.select{|k,v| v.length>1 }.
        each{|k,v| puts "#{k} #{v.uniq.length}"} 
}
' file.txt

Распечатки:

ABC123 2
ABCDEF 2
DEF123 1

Неясно, должен ли CCCCCC 1 присутствовать в выводе. Если да, то фильтровать не нужно:

ruby -lane 'BEGIN{ cnt=Hash.new{|h,k| h[k]=[]} }
cnt[$F[0]]<<$F[1]
END{ cnt.each{|k,v| puts "#{k} #{v.uniq.length}"} }
' file.txt

Распечатки:

ABC123 2
ABCDEF 2
CCCCCC 1
DEF123 1

Вы также можете сделать этот канал POSIX:

uniq file.txt | awk '{cnt[$1]++} END{for (e in cnt) print e, cnt[e]}'

Распечатки:

CCCCCC 1
ABCDEF 2
DEF123 1
ABC123 2