Zmodyfikuj niektóre źle rozdzielone dane w przydatny plik CSV

13

Mam pewne dane wyjściowe w postaci:

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
...

Co jest dość niechlujne i musi zostać wyczyszczone do pliku CSV, abym mógł przekazać go kierownikowi projektu dla nich arkusz kalkulacyjny.

Rdzeń problemu jest następujący: potrzebuję tego:

id, suma_typu_1, suma_typu_2, suma_typu_3

Przykładem tego jest identyfikator „4”:

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

Zamiast tego powinno to być:

4,15,253,19871

Niestety jestem dość śmieci w tego rodzaju sprawach, udało mi się wyczyścić wszystkie wiersze i przejść do pliku CSV, ale nie byłem w stanie deduplikować i pogrupować wierszy. Teraz mam to:

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

Ale wszystko, co robi, to sprzątanie śmieci i ponowne drukowanie wierszy.

Jaki jest najlepszy sposób na masowanie wierszy do wyżej wspomnianego wyjścia?

Paweł
źródło
Czy w ogóle chcesz sumować liczby?
hjk

Odpowiedzi:

12

Sposobem na to jest umieszczenie wszystkiego w haszu.

# 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];
    }
}'

edycja: moja pierwsza odpowiedź nie odpowiedziała poprawnie na pytanie

Ciemne serce
źródło
Tak, udało się to bardzo dobrze. Dzięki! Jedyną rzeczą jest to, że nie brałem pod uwagę, że niektóre typy identyfikatorów są puste, a tym samym psują CSV, ale mogę dopracować ten drobiazg
Paul
@Paul Może dodaj NF<4{$4="no_type";}na początku
DarkHeart
11

Perl na ratunek:

#!/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 };
}

Przechowuje dwie tabele, tablicę typów i tablicę identyfikatorów. Dla każdego identyfikatora przechowuje sumę według typu.

choroba
źródło
5

Jeśli GNU datamash jest dla ciebie opcją, to

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
steeldriver
źródło
4

Python (a pandaszwłaszcza biblioteka jest bardzo odpowiednia do tego rodzaju pracy)

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')

To odczytuje dane csv do 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

Następnie grupujemy te dane według idi bierzemy sumę kolumnycount

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

unstack Przekształca to, aby przesunąć id do kolumn, a fillnawypełnia puste pola z 0-tych

df_sum.to_csv()

To zwraca

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

Ponieważ ramka danych zawiera brakujące dane (puste kombinacje typu id), panda przekształca ints na float(ograniczenie wewnętrznych działań). Jeśli wiesz, że dane wejściowe będą tylko int, możesz zmienić następny na ostatni wiersz nadf_sum = df.groupby(('type', 'id'))['count'].sum().unstack('type').fillna(0).astype(int)

Maarten Fabré
źródło
1
Powinieneś wyjaśnić, co robi kod, który podałeś, więc jest pomocny dla wszystkich, którzy widzą ten post, a nie dla tej konkretnej osoby.
Pozew funduszu Moniki z
Czy to jest jaśniejsze? Poprawiłem także wyrażenie regularne dla separatora
Maarten Fabré
Dla mnie wygląda dobrze. Dziękujemy za dodanie wyjaśnienia!
Pozew Fund Moniki z
3

Możesz użyć Perla do zapętlenia pliku CSV i gromadzenia sumy odpowiednich typów w haszu podczas podróży. Na koniec wyświetl informacje zebrane dla każdego identyfikatora.

Struktura danych

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

Pomaga to zrozumieć poniższy kod:

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

Wynik

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

źródło
1

moje zdanie nie różni się zbytnio od innych. Używa GNU awk, który ma tablice tablic

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

wyjścia

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
Glenn Jackman
źródło
0

Możesz użyć tego kodu do podsumowania wartości na podstawie kolumny identyfikatora,

Dodałem jedną instrukcję awk po twoim kodzie

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];}'

Śmiało z tym ...

Prem Joshi
źródło