Bearbeiten Sie einige schlecht begrenzte Daten in einer nützlichen CSV

13

Ich habe einige Ausgaben in Form von:

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

Das ist ziemlich chaotisch und muss zu einer CSV aufgeräumt werden, damit ich es einem Projektmanager schenken kann.

Der Kern des Problems ist dieser: Ich brauche die Ausgabe von diesem zu sein:

id, sum_of_type_1, sum_of_type_2, sum_of_type_3

Ein Beispiel hierfür ist die ID "4":

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

Dies sollte stattdessen sein:

4,15,253,19871

Leider bin ich ziemlich verrückt bei so etwas. Ich habe es geschafft, alle Zeilen zu bereinigen und in CSV zu übertragen, aber ich konnte die Zeilen nicht deduplizieren und gruppieren. Im Moment habe ich das:

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

Aber alles, was Sie tun müssen, ist, den Müll aufzuräumen und die Zeilen erneut auszudrucken.

Wie lassen sich die Zeilen am besten in die oben genannte Ausgabe einmassieren?

Paul
quelle
Möchten Sie die Zählungen überhaupt zusammenfassen?
hjk

Antworten:

12

Eine Möglichkeit, dies zu tun, besteht darin, alles in einen Hash zu setzen.

# 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: meine erste antwort hat die frage nicht richtig beantwortet

Dunkles Herz
quelle
Ja, das hat den Trick sehr gut gemacht. Vielen Dank! Das Einzige ist, dass ich nicht berücksichtigt habe, dass einige Typen von IDs leer sind und somit die CSV durcheinander bringen, aber ich kann dieses kleine Detail herausarbeiten
Paul
@ Paul Vielleicht NF<4{$4="no_type";}zu Beginn hinzufügen
DarkHeart
11

Perl zur Rettung:

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

Es enthält zwei Tabellen, eine Typentabelle und eine ID-Tabelle. Für jede ID wird die Summe pro Typ gespeichert.

Choroba
quelle
5

Wenn GNU Datamash eine Option für Sie ist, dann

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
Stahlfahrer
quelle
4

Python (und insbesondere die pandasBibliothek eignen sich sehr gut für diese Art von Arbeit

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

Hiermit werden die CSV-Daten in a gelesen 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

Dann gruppieren wir diese Daten nach idund nehmen die Summe der Spaltencount

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

Das unstack ändert seine Form , um die IDs in die Spalten zu verschieben, und fillnafüllt die leeren Felder mit Nullen

df_sum.to_csv()

Dies kehrt zurück

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

Da der Datenrahmen fehlende Daten enthält (leere ID-Typ-Kombinationen), wandelt pandas das ints in um float(Einschränkung der internen Funktionsweise). Wenn Sie wissen, dass die Eingaben nur int sind, können Sie die vorletzte Zeile in änderndf_sum = df.groupby(('type', 'id'))['count'].sum().unstack('type').fillna(0).astype(int)

Maarten Fabré
quelle
1
Sie sollten erklären, was der von Ihnen bereitgestellte Code bewirkt, damit er für alle Personen hilfreich ist, die diesen Beitrag sehen, und nicht für diese eine bestimmte Person.
Fund Monica Klage
Ist das klarer? Ich habe auch die Regex für den Trenner
Maarten Fabré
Sieht gut für mich aus. Vielen Dank für das Hinzufügen einer Erklärung!
Fund Monica's Lawsuit
3

Sie können Perl verwenden, um die CSV-Datei zu durchlaufen und die Summe der entsprechenden Typen in einem Hash zu akkumulieren, während Sie unterwegs sind. Zeigen Sie am Ende die Informationen an, die für jede ID gesammelt wurden.

Datenstruktur

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

Dies hilft, den folgenden Code zu verstehen:

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

Ausgabe

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

quelle
1

Meiner Meinung nach nicht zu verschieden von anderen. Verwendet GNU awk mit Arrays von Arrays

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

Ausgänge

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
quelle
0

Mit diesem Code können Sie Werte basierend auf Ihrer ID-Spalte zusammenfassen.

Ich habe eine awk-Anweisung nach Ihrem Code hinzugefügt

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

Fahren Sie fort mit diesem ...

Prem Joshi
quelle