
У меня есть более 5000 строк, разделенных пробелами, как показано ниже:
Item_A: Acou#1 Bla#5
Item_B: Acou#1 Elfa#2 Flq#2
Item_C: Acou#1 Bla#4 Elfa#2 Flq#2
Item_D: Agly#3 Bla#4 Elfa#2
Я хочу создать таблицу с общими заголовками для всех и количеством для каждого элемента в таблице, как показано ниже.
Acou Agly Bla Elfa Flq
Item_A: 1 0 5 0 0
Item_B: 1 0 0 2 2
Item_C: 1 0 4 2 2
Item_D: 0 3 4 2 0
Я использовал grep для строк, содержащих "Acou", затем "Bla" и т. д. Затем редактировал в Excel, чтобы количественно оценить их и объединить все отдельные файлы в один файл. Однако это занимало много времени.
решение1
BEGIN { OFS = "\t" }
# Collect headers from data
FNR == NR {
for (i = 2; i <= NF; ++i)
if (!($i in heads))
heads[$i]
next
}
# Output header
FNR == 1 {
line = "Items"
for (j in heads)
line = line OFS j
print line
}
{
line = $1
# Iterate through the header items, testing each field against it
for (j in heads) {
found = 0 # assume not found
for (i = 2; !found && i <= NF; ++i)
if ($i == j)
found = 1 # matches header
line = line OFS found
}
print line
}
Запускаем это на ваших данных (после удаления пустых строк):
$ awk -f script.awk file file
Items Acou#1 Bla#4 Bla#5 Elfa#2 Agly#3 Flq#2
Item_A: 1 0 1 0 0 0
Item_B: 1 0 0 1 0 1
Item_C: 1 1 0 1 0 1
Item_D: 0 1 0 1 1 0
Обратите внимание, что вам нужно указать файл входных данных дважды. Это связано с тем, что мы сканируем его дважды. При первом сканировании мы собираем элементы данных в каждой строке (блок FNR == NR
). При втором сканировании мы проверяем каждый собранный элемент данных (заголовки) по отношению к данным в каждой строке.
Вывод просто, 0
если поле в заголовке отсутствует в данных на этой строке, и 1
если оно есть. Это недовольното, о чем вы просили, так что...
Вариант, который обрезает заголовки в точке #
и использует часть после #
в качестве данных для отображения:
BEGIN { OFS = "\t" }
# Collect headers from data
FNR == NR {
for (i = 2; i <= NF; ++i) {
split($i, h, "#")
if (!(h[1] in heads))
heads[h[1]]
}
next
}
# Output header
FNR == 1 {
line = "Items"
for (j in heads)
line = line OFS j
print line
}
{
line = $1
# Iterate through the header items, testing each field against it
for (j in heads) {
found = 0 # assume not found
for (i = 2; !found && i <= NF; ++i) {
split($i, h, "#")
if (h[1] == j)
found = h[2] # matches header
}
line = line OFS found
}
print line
}
Запуск:
$ awk -f script.awk file file
Items Elfa Bla Acou Agly Flq
Item_A: 0 5 1 0 0
Item_B: 2 0 1 0 2
Item_C: 2 4 1 0 2
Item_D: 2 4 0 3 0
Обратите внимание, что порядок столбцов не обязательно отсортирован (поскольку они хранятся как ключи в ассоциативном массиве). Я оставляю читателю в качестве упражнения сортировку.
решение2
Если вы не против броситьGNU-датамашв смесь, то вы можете просто сериализовать записи, а затем составить их перекрестную таблицу:
awk '
{for (i=2;i<=NF;i++) {split($i,a,"#"); print $1,a[1],a[2]}}' OFS='\t' file |
datamash --filler=0 crosstab 1,2 count 3
Acou Agly Bla Elfa Flq
Item_A: 1 0 1 0 0
Item_B: 1 0 0 1 1
Item_C: 1 0 1 1 1
Item_D: 0 1 1 1 0
Альтернативный вариант — с помощью GNU awk (который позволяет использовать многомерные массивы):
gawk '
BEGIN {
OFS="\t";
PROCINFO["sorted_in"] = "@ind_str_asc";
}
{
for (i=2;i<=NF;i++) {
split($i,a,"#");
h[a[1]] = 1;
t[$1][a[1]] += a[2];
}
}
END {
for (j in h) printf("\t%s", j);
printf "\n";
for (i in t) {
printf("%s",i);
for (j in h)
printf("\t%d", j in t[i] ? t[i][j] : 0);
printf "\n";
}
}' file
Acou Agly Bla Elfa Flq
Item_A: 1 0 5 0 0
Item_B: 1 0 0 2 2
Item_C: 1 0 4 2 2
Item_D: 0 3 4 2 0