Manipulez des données mal délimitées dans un CSV utile

13

J'ai une sortie sous la forme de:

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

Ce qui est assez désordonné et doit être nettoyé au format CSV afin que je puisse le donner à un gestionnaire de projet pour lui la feuille de calcul.

Le cœur du problème est le suivant: j'ai besoin que la sortie de ceci soit:

id, sum_of_type_1, sum_of_type_2, sum_of_type_3

Un exemple de ceci est l'ID "4":

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

Cela devrait plutôt être:

4,15,253,19871

Malheureusement, je suis assez nul à ce genre de chose, j'ai réussi à nettoyer toutes les lignes et à les convertir en CSV, mais je n'ai pas pu dédupliquer et grouper les lignes. En ce moment, j'ai ceci:

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

Mais tout ce que cela fait, c'est nettoyer les caractères de déchets et réimprimer les lignes.

Quelle est la meilleure façon de masser les lignes dans la sortie mentionnée ci-dessus?

Paul
la source
Voulez-vous même additionner les chiffres?
hjk

Réponses:

12

Une façon de le faire est de tout mettre dans un hachage.

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

modifier: ma première réponse n'a pas répondu correctement à la question

Cœur sombre
la source
Oui, cela a très bien fait l'affaire. Merci! La seule chose est que je n'ai pas expliqué que certains types d'ID étaient vides et gâchaient ainsi le CSV, mais je peux travailler ce petit détail
Paul
@Paul Peut-être ajouter NF<4{$4="no_type";}au début
DarkHeart
11

Perl à la rescousse:

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

Il conserve deux tables, une table des types et une table des identifiants. Pour chaque id, il stocke la somme par type.

choroba
la source
5

Si le datamash GNU est une option pour vous, alors

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
tournevis
la source
4

Python (et la pandasbibliothèque en particulier est très adaptée à ce genre de travail

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

Cela lit les données csv dans un 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

Ensuite, nous regroupons ces données par id, et prenons la somme des colonnescount

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

Le unstack remodèle ceci pour déplacer les identifiants vers les colonnes, et fillnaremplit les champs vides avec des 0

df_sum.to_csv()

Cela revient

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

Étant donné que la trame de données contient des données manquantes (combinaisons de type id vides), pandas transforme le ints en float(limitation du fonctionnement interne). Si vous savez que les entrées seront uniquement int, vous pouvez modifier la dernière ligne suivante endf_sum = df.groupby(('type', 'id'))['count'].sum().unstack('type').fillna(0).astype(int)

Maarten Fabré
la source
1
Vous devez expliquer ce que fait le code que vous avez fourni, il est donc utile à tous ceux qui voient ce message, plutôt qu'à cette personne en particulier.
Fund Monica's Lawsuit
Est-ce plus clair? J'ai également corrigé l'expression régulière du séparateur
Maarten Fabré
Cela me semble correct. Merci d'avoir ajouté une explication!
Fund Monica's Lawsuit
3

Vous pouvez utiliser Perl pour parcourir le fichier CSV et accumuler la somme des types appropriés dans un hachage en cours de route. Et à la fin, affichez les informations collectées pour chaque ID.

Structure de données

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

Cela aide à donner un sens au code ci-dessous:

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

Production

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

la source
1

mon point de vue, pas trop différent des autres. Utilise GNU awk qui a des tableaux de tableaux

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

les sorties

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
la source
0

Vous pouvez utiliser ce code pour résumer les valeurs en fonction de votre colonne id,

J'ai ajouté une déclaration awk après votre code

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

Allez-y avec ça ...

Prem Joshi
la source