Intereting Posts

Манипулировать некоторые данные с ограниченным разделением в полезный CSV

У меня есть выход в виде:

count id type 588 10 | 3 10 12 | 3 883 14 | 3 98 17 | 3 17 18 | 1 77598 18 | 3 10000 21 | 3 17892 2 | 3 20000 23 | 3 63 27 | 3 6 3 | 3 2446 35 | 3 14 4 | 3 15 4 | 1 253 4 | 2 19857 4 | 3 1000 5 | 3 ... 

Это довольно грязно, и его нужно очистить до CSV, чтобы я мог подарить его менеджеру проекта для них, как черта.

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

id, sum_of_type_1, sum_of_type_2, sum_of_type_3

Примером этого является id «4»:

 14 4 | 3 15 4 | 1 253 4 | 2 19857 4 | 3 

Это должно быть:

 4,15,253,19871 

К сожалению, я довольно мусор в таких вещах, мне удалось очистить все строки и в CSV, но я не смог дедуплицировать и группировать строки. Прямо сейчас у меня это:

 awk 'BEGIN{OFS=",";} {split($line, part, " "); print part[1],part[2],part[4]}' | awk '{ gsub (" ", "", $0); print}' 

Но все, что делает, очищает мусорные символы и печатает строки снова.

Каков наилучший способ массирования строк в вышеупомянутый вывод?

Способ сделать это – положить все в хэш.

 # put values into a hash based on the id and tag awk 'NR>1{n[$2","$4]+=$1} END{ # merge the same ids on the one line for(i in n){ id=i; sub(/,.*/,"",id); a[id]=a[id]","n[i]; } # print everyhing for(i in a){ print i""a[i]; } }' 

edit: мой первый ответ не ответил на вопрос правильно

Перл на помощь:

 #!/usr/bin/perl use warnings; use strict; use feature qw{ say }; <>; # Skip the header. my %sum; my %types; while (<>) { my ($count, $id, $type) = grep length, split '[\s|]+'; $sum{$id}{$type} += $count; $types{$type} = 1; } say join ',', 'id', sort keys %types; for my $id (sort { $a <=> $b } keys %sum) { say join ',', $id, map $_ // q(), @{ $sum{$id} }{ sort keys %types }; } 

Он хранит две таблицы, таблицу типов и таблицу идентификаторов. Для каждого id он сохраняет сумму за тип.

Если GNU datamash является для вас вариантом, то

 awk 'NR>1 {print $1, $2, $4}' OFS=, file | datamash -t, -s --filler=0 crosstab 2,3 sum 1 ,1,2,3 10,0,0,588 12,0,0,10 14,0,0,883 17,0,0,98 18,17,0,77598 2,0,0,17892 21,0,0,10000 23,0,0,20000 27,0,0,63 3,0,0,6 35,0,0,2446 4,15,253,19871 5,0,0,1000 

Python (и, в частности, библиотека pandas очень подходит для такого рода работ

 data = """count id type 588 10 | 3 10 12 | 3 883 14 | 3 98 17 | 3 17 18 | 1 77598 18 | 3 10000 21 | 3 17892 2 | 3 20000 23 | 3 63 27 | 3 6 3 | 3 2446 35 | 3 14 4 | 3 15 4 | 1 253 4 | 2 19857 4 | 3 1000 5 | 3""" import pandas as pd from io import StringIO # to read from string, not needed to read from file df = pd.read_csv(StringIO(data), sep=sep='\s+\|?\s*', index_col=None, engine='python') 

Это считывает данные csv в pandas DataFrame

  count id type 0 588 10 3 1 10 12 3 2 883 14 3 3 98 17 3 4 17 18 1 5 77598 18 3 6 10000 21 3 7 17892 2 3 8 20000 23 3 9 63 27 3 10 6 3 3 11 2446 35 3 12 14 4 3 13 15 4 1 14 253 4 2 15 19857 4 3 16 1000 5 3 

Затем мы группируем эти данные по id и принимаем сумму столбца

 df_sum = df.groupby(('type', 'id'))['count'].sum().unstack('type').fillna(0) 

unstack перетаскивает это, чтобы переместить id в столбцы, а fillna заполняет пустые поля 0

 df_sum.to_csv() 

Это возвращает

 id,1,2,3 2,0.0,0.0,17892.0 3,0.0,0.0,6.0 4,15.0,253.0,19871.0 5,0.0,0.0,1000.0 10,0.0,0.0,588.0 12,0.0,0.0,10.0 14,0.0,0.0,883.0 17,0.0,0.0,98.0 18,17.0,0.0,77598.0 21,0.0,0.0,10000.0 23,0.0,0.0,20000.0 27,0.0,0.0,63.0 35,0.0,0.0,2446.0 

Поскольку в кадре данных отсутствуют отсутствующие данные (пустые комбинации типа id), pandas преобразует int s в float (ограничение внутренних операций). Если вы знаете, что входные данные wil только int, вы можете изменить следующую строку на df_sum = df.groupby(('type', 'id'))['count'].sum().unstack('type').fillna(0).astype(int)

Вы можете использовать Perl для циклического перемещения по CSV-файлу и аккумулирования суммы соответствующих типов в хеше во время пути. И в конце отобразите информацию, собранную для каждого идентификатора.

Структура данных

 %h = ( ID1 => [ sum_of_type1, sum_of_type2, sum_of_type3 ], ... ) 

Это помогает понять код ниже:

Perl

 perl -wMstrict -Mvars='*h' -F'\s+|\|' -lane ' $, = chr 44, next if $. == 1; my($count, $id, $type) = grep /./, @F; $h{ $id }[ $type-1 ] += $count}{ print $_, map { $_ || 0 } @{ $h{$_} } for sort { $a <=> $b } keys %h ' yourcsvfile 

Вывод

 2,0,0,17892 3,0,0,6 4,15,253,19871 5,0,0,1000 ... 

мой прием, не слишком отличающийся от других. Использует GNU awk с массивами массивов

 gawk ' NR == 1 {next} {count[$2][$4] += $1} END { for (id in count) { printf "%d", id for (type=1; type<=3; type++) { # add zero to coerce possible empty string into a number printf ",%d", 0 + count[id][type] } print "" # adds the newline for this line } } ' file 

выходы

 2,0,0,17892 3,0,0,6 4,15,253,19871 5,0,0,1000 10,0,0,588 12,0,0,10 14,0,0,883 17,0,0,98 18,17,0,77598 21,0,0,10000 23,0,0,20000 27,0,0,63 35,0,0,2446 

Вы можете использовать этот код для суммирования значений на основе столбца id,

Я добавил один оператор awk после вашего кода.

 awk 'BEGIN{OFS=",";} {split($line, part, " "); print part[1],part[2],part[4]}' abcd | awk '{ gsub (" ", "", $0); print}' | awk 'BEGIN{FS=OFS=SUBSEP=","}{arr[$2,$3]+=$1;}END{for ( i in arr ) print i,arr[i];}' 

Идем дальше …