Eu tenho um arquivo csv com entradas de dados de amostra da seguinte forma:
Timestamp,data1,data2
2018 07 16 13:00:00,23,45
2018 07 16 13:10:00,23,45
2018 07 16 13:20:00,23,45
2018 07 16 13:30:00,23,45
2018 07 16 13:50:00,23,45
2018 07 16 14:20:00,23,45
2018 07 16 14:40:00,23,45
2018 07 16 14:50:00,23,45
2018 07 16 15:10:00,23,45
2018 07 16 17:50:00,23,45
2018 07 16 18:10:00,23,45
2018 07 17 10:10:00,23,45
2018 07 18 13:20:00,23,45
2018 07 19 13:30:00,23,45
O que eu gostaria de fazer é criar outras 2 colunas, Date
& Hour
. A Date
coluna conterá a data e Hour
todas as horas em que os dados foram capturados. Por exemplo, com base nos dados acima, gostaria de ter a seguinte saída (mesmo arquivo, apenas adicionando 2 colunas extras):
Date,Hour
2018 07 16,13
2018 07 16,14
2018 07 16,15
2018 07 16,17
2018 07 16,18
2018 07 17,10
2018 07 18,13
2018 07 19,13
Por exemplo, se houver entradas na hora 13 (seja 1 ou muitas) em 2018 07 16, liste a data e hora 13 correspondentes apenas uma vez e passe para entradas com horas diferentes até a data mudar. e o processo se repete.
Observe que o arquivo tem muitas entradas (100.000+) para muitos dias, com número variável de dados capturados em uma hora, conforme acima. Como posso resolver esse problema? Espero que minha explicação seja clara o suficiente.
Responder1
Usando awk
:
awk 'BEGIN{ OFS=FS="," }
NR==1{ print "Date", "Hour"; next }
{
$0=substr($1, 1, 10) FS substr($1, 12, 2)
if ($0 == prev) next # skip to next record if record equals prev
prev=$0 # remember record
}
1 # print record
' file
Assim, a string de data consiste nos primeiros 10 caracteres começando na posição 1 do primeiro campo e a hora é extraída de 2 caracteres começando na posição 12.
Ambos os valores mais um separador de campo ( FS
) são atribuídos ao registro ( $0
) e impressos se o registro anterior lembrado for diferente.
Responder2
sort
e uniq
pode fornecer o exemplo de saída mostrado na sua pergunta.
$ sed -e 's/Timestamp.*/Date,Hour/; s/ \(..\):.*/,\1/' file.csv | uniq
Date,Hour
2018 07 16,13
2018 07 16,14
2018 07 16,15
2018 07 16,17
2018 07 16,18
2018 07 17,10
2018 07 18,13
2018 07 19,13
No entanto, você também disse que queria esses dois novos campos anexados às linhas de entrada atuais. Isso não faz muito sentido para mim, porque aí você acabaria com a Data e a Hora duplicadas em cada linha (elas já estão no início de cada linha no campo Timestamp).
O seguinte não é exatamente o que você pediu, mas é, na IMO, uma melhoria.
Em vez de anexar Data e Hora ao final de cada linha, ele apenas sed
transforma o campo Timestamp existente em campos Data e Hora. Em seguida, uniq
é usado para eliminar linhas duplicadas.
$ sed -e 's/Timestamp/Date,Hour/; s/ \(..\):[^,]*,/,\1,/' file.csv | uniq
Date,Hour,data1,data2
2018 07 16,13,23,45
2018 07 16,14,23,45
2018 07 16,15,23,45
2018 07 16,17,23,45
2018 07 16,18,23,45
2018 07 17,10,23,45
2018 07 18,13,23,45
2018 07 19,13,23,45
Isso pressupõe que o arquivo de entrada já esteja na ordem do carimbo de data/hora.
NOTA: se os valores para data1
ou data2
puderem variar, as linhas de saída não serão exclusivas e a linha será impressa. Isso ocorre porque uniq
compara a linha inteira com a linha anterior ( uniq
pode ser feito para pular campos, mas reconhece apenas espaços em branco como um separador de campo e não pode ser usado para usar vírgulas, nem pode ser usado para usar apenas os dois primeiros campos) . Se é isso que você quer, então funcionará como está.
Caso contrário, você precisará usar awk
or perl
ou algo em vez de uniq
verificar a exclusividade. por exemplo, os seguintes usos awk
para comparar apenas os dois primeiros campos separados por vírgula (ou seja, Data e Hora):
$ sed -e 's/Timestamp/Date,Hour/; s/ \(..\):[^,]*,/,\1,/' file.csv |
awk -F, 'prev != $1$2 {print; prev=$1$2}'
Mas se você for canalizar a saída para sed
into awk
, você pode muito bem usar awk
sozinho, já que o awk pode fazer tudo o que sed
pode fazer - é para isso que servem as funções sub()
, gsub()
e gensub()
do awk. por exemplo
$ awk -F, -v OFS=, '{ sub(/Timestamp/,"Date,Hour");
$1 = gensub(/ ([0-9]+):.*/,",\\1",1,$1)
};
prev != $1$2 {print; prev=$1$2}' file.csv
ou com perl
:
$ perl -lne 's/Timestamp/Date,Hour/;
s/ (\d\d):.*?,/,$1,/;
($current) = (m/^[^,]+,\d\d|^Date),/);
if ($prev ne $current) {print ; $prev = $current}' file.csv