Como agrupar variáveis com base no intervalo de tempo? [fechadas]

1

Meu arquivo fb.csv é o seguinte:

"Source","Time"  
"192.168.137.174","120025"
"10.0.138.163","120525"
"157.240.10.13","121036"
"157.240.10.13","122536"
"157.240.10.23","123041"
"157.240.10.23","123241"
"10.0.138.163","123352"
"192.168.137.174","123952"
"157.240.10.18","124152"
"157.240.10.18","124252"
"157.240.10.23","125653"
"157.240.10.23","130053"
"192.168.137.174","130102"
"10.0.138.163","130302"
"192.168.137.174","131007"
"192.168.137.174","131352"
"157.240.10.18","132552"
"157.240.10.18","132752"
"157.240.10.23","132953"
"157.240.10.23","133253"
"192.168.137.174","133502"
"10.0.138.163","134002"
"192.168.137.174","134507"
"192.168.137.174","135752"
"157.240.10.18","140052"
"157.240.10.18","140552"
"157.240.10.23","140653"
"157.240.10.23","141053"
"192.168.137.174","141402"
"10.0.138.163","141702"
"192.168.137.174","142707"

Gostaria de agrupar (contar) "Fontes" para cada intervalo de tempo de 003000 (30 minutos).

Exemplo de saída desejada:

"Time Interval","Count of Sources"
"120000","4"
"123000","7"
"130000","8"
"133000","5"
"140000","7"

Existe alguma solução possível para este assunto? Obrigado.

    
por Ayu 14.09.2017 / 22:02

2 respostas

1

Podemos fazer isso inteiramente usando apenas awk :

awk -F, 'BEGIN{print"\"Time Interval\",\"Count of Sources\""}NR>1{gsub(/"/,"",);h=int(/10000)*10000;m=int((-h)/3000)*3000;ctr[h+m]++}END{n=asorti(ctr,idx);for(i=1;i<=n;i++){print "\""idx[i]"\",\""ctr[idx[i]]"\""}}' fb.csv

Para o arquivo de entrada fornecido fb.csv , isso resulta na saída

"120000","4"
"123000","7"
"130000","8"
"133000","5"
"140000","7"

Importante: Isso requer que o GNU AWK ( gawk ) seja executado, porque ele usa a função asorti(...) para classificar matrizes associativas por seus índices. Não funciona com mawk . Você pode descobrir sua versão awk padrão usando awk -Wv .

Explicação do comando:

Corremos awk assim, definindo o separador de campos que delimita as colunas para , e usando o arquivo fb.csv como entrada:

awk -F, '<COMMAND>' fb.csv

O comando awk (espaço reservado <COMMAND> acima) é este, após a formatação adequada:

BEGIN {
    print "\"Time Interval\",\"Count of Sources\""
}
NR>1 {
    gsub(/"/, "", )
    h = int( / 10000) * 10000
    m = int((-h) / 3000) * 3000
    ctr[h+m]++
}
END {
    n = asorti(ctr, idx)
    for(i=1; i<=n; i++) {
        print "\"" idx[i] "\",\"" ctr[idx[i]] "\""
    }
}

Isso parece terrivelmente complicado (e não posso negar que é preciso pensar um pouco para entender), então vou tentar dividir um pouco:

O bloco de código BEGIN { ... } será executado uma vez antes da primeira linha de entrada do arquivo ser lida. Então, para cada linha exceto a primeira ("número de linha maior que 1"), o bloco NR>1 { ... } é executado. Finalmente, depois que toda a entrada for lida, o bloco END { ... } será executado.

  • Agora, o bloco BEGIN é bastante direto, ele imprime apenas a nova linha de cabeçalho CSV.

  • Vamos ver o bloco NR>1 . Lembre-se que awk divide cada linha em campos, que foram separados pelo delimitador de campo (aquele que definimos como , usando o argumento -F ). A primeira coluna / campo será armazenada na variável , a segunda em e assim por diante. Estamos interessados apenas no valor do segundo campo, que contém o tempo.

    Usando a função gsub(<PATTERN>, <REPLACEMENT>, <VARIABLE>) , substituímos todas as ocorrências de <PATTERN> (uma expressão regular incluída em barras, aqui ela simplesmente corresponde apenas às aspas) com um <REPLACEMENT> (vazio, pois queremos removê-las) string em <VARIABLE> ( ou seja, o segundo campo contendo o tempo aqui).

    Em seguida, decodificamos o registro de data e hora em horas inteiras h (multiplicado por 10000) e metade das horas inteiras m (sem as horas completas; multiplicado por 3000). Usamos uma matriz associativa ctr como contador de quantas vezes o carimbo de hora arredondado h+m ocorre na entrada.

  • Finalmente, no bloco END , imprimimos os valores do contador classificados pelo registro de data e hora arredondado índices.

por Byte Commander 14.09.2017 / 23:47
0

Tenho certeza de que há uma maneira mais elegante, mas minha sugestão é criar um arquivo executável, vamos chamá-lo de counter.bash , com este conteúdo de script:

#!/bin/bash

echo '"Time Interval","Count of Sources"'
FILTRED=$(tail -n +2 "" | sed -e 's/^.*\,//' -e 's/"//g' | sort)
T=1

while [ $T -lt 24 ]; do

        ((T++)); R1=0; R2=0

        for i in $FILTRED; do

                hour=${i::-4}; minute=${i:2:-2}

                if [ "$T" -lt "10" ]; then TT="0${T}"; else TT="${T}"; fi

                if [ "$minute" -lt "30" ]; then
                        if [ "$hour" == "$TT" ]; then ((R1++)); fi
                else
                        if [ "$hour" == "$TT" ]; then ((R2++)); fi
                fi
        done

        if [ "$R1" -ne "0" ]; then echo "\"${TT}0000\",\"$R1\""; fi
        if [ "$R2" -ne "0" ]; then echo "\"${TT}3000\",\"$R2\""; fi
done

Em seguida, execute:

./counter.bash fb.csv

Se o resultado for adequado, redirecione a saída para um novo arquivo:

./counter.bash fb.csv > fb.counted.csv

Eu comparei o desempenho da resposta do Byte Commander e este script, ambos aplicados no mesmo arquivo não tão grande. Eles são apenas incomparáveis:

$ cat fb.csv | wc -l
3304


$ time awk -F, 'BEGIN{print"\"Time Interval\",\"Count of Sources\""}NR>1{gsub(/"/,"",);h=int(/10000)*10000;m=int((-h)/3000)*3000;ctr[h+m]++}END{n=asorti(ctr,idx);for(i=1;i<=n;i++){print "\""idx[i]"\",\""ctr[idx[i]]"\""}}' fb.csv

"Time Interval","Count of Sources"
"120000","672"
"123000","672"
"130000","560"
"133000","560"
"140000","839"

real    0m0.017s
user    0m0.012s
sys     0m0.000s


$ time ./counter.bash fb.csv

"Time Interval","Count of Sources"
"120000","672"
"123000","672"
"130000","560"
"133000","560"
"140000","839"

real    0m2.374s
user    0m2.368s
sys     0m0.000s

Quando o arquivo é realmente grande, meu script falha (em linhas diferentes) após um longo período:

$ cat fb.csv | wc -l
9303745


$ time awk -F, 'BEGIN{print"\"Time Interval\",\"Count of Sources\""}NR>1{gsub(/"/,"",);h=int(/10000)*10000;m=int((-h)/3000)*3000;ctr[h+m]++}END{n=asorti(ctr,idx);for(i=1;i<=n;i++){print "\""idx[i]"\",\""ctr[idx[i]]"\""}}' fb.csv

"Time Interval","Count of Sources"
"120000","1892288"
"123000","1892290"
"130000","1576904"
"133000","1576905"
"140000","2365357"

real    0m23.193s
user    0m23.080s
sys     0m0.096s


$ time ./counter.bash fb.csv

"Time Interval","Count of Sources"
./counter.bash: line 17: [: .0.138.1: integer expression expected
^C
real    2m27.992s
user    2m27.940s
sys     0m1.636s
    
por pa4080 14.09.2017 / 23:51