Manipule algunos datos pobremente delimitados en un CSV útil

13

Tengo algo de salida en forma 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
...

Lo cual es bastante desordenado y necesita ser limpiado en un CSV para que pueda regalarle a un Gerente de Proyecto para ellos la hoja de cálculo.

El núcleo del problema es este: necesito que la salida de esto sea:

id, sum_of_type_1, sum_of_type_2, sum_of_type_3

Un ejemplo de esto es id "4":

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

Esto debería ser:

4,15,253,19871

Desafortunadamente, soy bastante basura en este tipo de cosas, he logrado limpiar todas las líneas y ponerlas en CSV, pero no he podido deduplicar y agrupar las filas. En este momento tengo esto:

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

Pero todo lo que hace es limpiar los caracteres de basura e imprimir las filas nuevamente.

¿Cuál es la mejor manera de masajear las filas en el resultado mencionado anteriormente?

Pablo
fuente
¿Incluso quieres sumar los recuentos juntos?
hjk

Respuestas:

12

Una forma de hacerlo es poner todo en un hash.

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

editar: mi primera respuesta no respondió la pregunta correctamente

Corazón oscuro
fuente
Sí, esto funcionó muy bien. ¡Gracias! Lo único es que no tomé en cuenta que algunos tipos de ID están vacíos y, por lo tanto, estropearon el CSV, pero puedo resolver ese pequeño detalle
Paul
@Paul Quizás agregue NF<4{$4="no_type";}al principio
DarkHeart
11

Perl al rescate:

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

Mantiene dos tablas, tabla de tipos y tabla de identificadores. Para cada id, almacena la suma por tipo.

choroba
fuente
5

Si GNU Datamash es una opción para usted, entonces

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
conductor de acero
fuente
4

Python (y la pandasbiblioteca en particular es muy adecuada para este tipo de trabajo

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

Esto lee los datos csv a 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

Luego grupo de los mismos por parte id, y tomamos la suma de la columnacount

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

El unstack forma esto para mover los id a las columnas, y fillnallena los campos vacíos con 0

df_sum.to_csv()

Esto vuelve

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

Debido a que el marco de datos contiene datos faltantes (combinaciones de tipo de identificación vacías), pandas transforma los ints en float(limitación del funcionamiento interno) Si sabe que las entradas serán solo int, puede cambiar la penúltima línea adf_sum = df.groupby(('type', 'id'))['count'].sum().unstack('type').fillna(0).astype(int)

Maarten Fabré
fuente
1
Debe explicar qué hace el código que ha proporcionado, por lo que es útil para todos los que ven esta publicación, en lugar de esta persona específica.
Financia la demanda de Mónica el
¿Es esto más claro? También corregí la expresión regular para el separador
Maarten Fabré
Me parece bien. ¡Gracias por agregar una explicación!
Fund Monica's Lawsuit
3

Puede usar Perl para recorrer el archivo CSV y acumular la suma de los tipos apropiados en un hash mientras está en camino. Y al final, muestre la información recopilada para cada ID.

Estructura de datos

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

Esto ayuda a dar sentido al código a continuación:

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

Salida

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

fuente
1

mi opinión, no muy diferente de los demás. Utiliza GNU awk que tiene matrices de matrices

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

salidas

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

Puede usar este código para resumir valores basados ​​en su columna de identificación,

He agregado una declaración awk después de su código

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

Adelante con esto ...

Prem Joshi
fuente