Como calcular a média do mês da coluna em particular com anos?

1

Eu tenho um arquivo de texto que tem dados de temperatura de abril e maio meses durante seis anos. Eu quero calcular a média de cada mês a cada ano. Estou usando o comando awk, mas calculo a média geral da temperatura. Eu não sei como usar o comando awk para este problema.

awk '{sum+=$6; n++} END {print sum/n;}' vk4.txt

O arquivo de amostra que estou mostrando,

STATION_ID,LATITUDE,LONGITUDE,TIME(GMT),DATE(GMT),AIR_TEMP(°C)
IMDE1611_14164B(PITAMPURA)  28.7    77.15   1   04/05/2012  31.4
IMDE1611_14164B(PITAMPURA)  28.7    77.15   2   04/05/2012  31.9
IMDE1611_14164B(PITAMPURA)  28.7    77.15   3   04/05/2012  32.6
IMDE1611_14164B(PITAMPURA)  28.7    77.15   2   05/01/2012  32.1
IMDE1611_14164B(PITAMPURA)  28.7    77.15   3   05/01/2012  32.3
IMDE1611_14164B(PITAMPURA)  28.7    77.15   4   05/01/2012  33
IMDE1611_14164B(PITAMPURA)  28.7    77.15   5   04/01/2013  33.9
IMDE1611_14164B(PITAMPURA)  28.7    77.15   6   04/01/2013  34.2
IMDE1611_14164B(PITAMPURA)  28.7    77.15   7   04/01/2013  34.8
    
por Vaibhav Kumar 03.06.2017 / 13:13

5 respostas

2

Outra - muito flexível - solução Python baseada em itertools.groupby : link

Instalação

wget https://github.com/davidfoerster/group-aggregate/raw/master/group-aggregate.py
chmod +x group-aggregate.py

Uso

./group-aggregate.py [--skip N] [options...] groups aggregators...
  • groups - Uma lista de índices de campo ou intervalos de coluna usados para agrupar registros (baseados em zero, separados por vírgulas).

  • aggregators - Um índice de campo (baseado em zero) ou intervalo de colunas, o nome de uma função de agregação e, opcionalmente, uma string de formato, todos separados por dois pontos.

  • --skip N - Ignora N linhas no início da entrada (por exemplo, linhas de cabeçalho).

Veja a saída de python3 -O group-aggregate.py --help para mais.

Exemplos

Exemplo 1

O programa de agrupamento e agregação não pode manipular campos parciais; vamos reformatar seu conjunto de dados com outras ferramentas para contornar:

awk '{ gsub(/\//, OFS, $5); print; }'  | ...

Agora, o campo de agrupamento, o ano, tem o índice 6 e o campo agregado, as temperaturas, tem o índice 7 do qual você deseja obter a média:

... | ./group-aggregate.py --skip 1 6 7:favg < data.csv

Você também pode formatar as médias de temperatura, neste exemplo para mostrar exatamente uma casa decimal :

... | ./group-aggregate.py --skip 1 6 7:favg:.1f

Exemplo 2

Em vez de separadores de campos, você também pode especificar intervalos de coluna que funcionam bem com seu formato de dados:

./group-aggregate.py --skip 1 54-58 60-:favg:.1f < data.csv

Agora você nem precisa pré-formatar os dados como no exemplo 1.

Saída

A saída dos dois comandos de exemplo é a mesma:

2012    32.2
2013    34.3
    
por David Foerster 24.07.2018 / 20:53
1

Você pode fazer isso com um pequeno script Python:

#!/usr/bin/env python3

import sys
if len(sys.argv) != 2:
    print("You must provide exactly one filename to read as argument.")
    exit(-1)

file = open(sys.argv[1])
file.readline()  # to strip headline

dict = {}
for line in file:
    datestr, tempstr = line.split()[4:]
    year, temp = int(datestr.split("/")[-1]), float(tempstr)
    dict.setdefault(year, []).append(temp)

for year in dict:
    print("{0}:\t{1:.2f}".format(year, sum(dict[year]) / len(dict[year])))

Ele lê o arquivo especificado como argumento ao executar o script linha por linha e cria um dicionário que mapeia anos para listas de valores de temperatura. Depois que o arquivo inteiro for processado, ele calculará e imprimirá as temperaturas médias por ano.

Aqui está um exemplo executado com o arquivo de dados vk4.txt que você forneceu. Salvei o script acima como avgtemp.py no diretório atual e tornei-o executável usando chmod +x avgtemp.py :

$ ./avgtemp.py vk4.txt
2012:   32.22
2013:   34.30

Se você quiser, o formato de saída exato pode ser facilmente modificado simplesmente editando a string "{0}:\t{1:.2f}" format na última linha do script. Você pode inserir qualquer padrão aqui, contanto que contenha {0} para ser substituído pelo ano e {1:.2f} ou similar para ser substituído pela temperatura média, exibida com dois dígitos decimais. O \t é uma guia.

    
por Byte Commander 03.06.2017 / 14:26
1

A ideia básica será criar uma chave de ano-mês a partir do campo de data e, em seguida, somar e contar as entradas com base nessa chave usando matrizes associativas, por exemplo.

awk '
  NR>1 {
    split($5,d,"/"); s[d[3]"/"d[1]]+=$6; c[d[3]"/"d[1]]++;
  } 
  END {
    for (i in s) print i, s[i]/c[i]
  }' vk4.txt

Teste com seus dados:

$ mawk '
  NR>1 {
    split($5,d,"/"); s[d[3]"/"d[1]]+=$6; c[d[3]"/"d[1]]++;
  } 
  END {
    for (i in s) print i, s[i]/c[i];
  }' vk4.txt
2012/04 31.9667
2012/05 32.4667
2013/04 34.3

Se você tem o GNU awk ( gawk ) v4 + você pode adicionar uma ordenação explícita.

    
por steeldriver 03.06.2017 / 14:33
0

Solução Perl

Aqui está um comando de uma linha, que opera com a premissa de criar dois hashes - $h1 para somar os valores de temperatura e $h2 para armazenar o número total de registros processados. Cada correspondente conterá a mesma chave no formato MMYYYY que é extraído de sua coluna # 5 (que é para perl array index # 4, ou seja, $F[4] ):

perl -lane 'do{ @a=split "/",$F[4]; $k= $a[0] . $a[2]; $h1{$k}+=$F[5] and $h2{$k}+=1 } if $. != 1 and $F[4]; END{ do {print $_," ",$h1{$_}/$h2{$_}  } for keys %h1;  }'

Ponto-chave a ser observado aqui:

  • usamos do {} if condition1 and condition2 estrutura. A ação {} é executada somente quando o número da linha não é 1 (ou seja, ignoramos o cabeçalho) e há $F[4] (isto é, evitamos linhas em branco ou incompletas).

  • @a=split "/",$F[4] nos permite dividir MM/DD/YYYY data stamp em partes e com $k= $a[0] . $a[2] criamos variável de chave que nos permitirá armazenar os dados em dois hashes.

  • END{} structure executará ações quando todo o arquivo tiver sido lido.

A solução funciona razoavelmente bem. Aqui está um teste com 1.100.000 linhas de entrada:

bash-4.3$ time perl -lane 'do{ @a=split "/",$F[4]; $k= $a[0] . $a[2]; $h1{$k}+=$F[5] and $h2{$k}+=1 } if $. != 1 and $F[4]; END{ do {print $_," ",$h1{$_}/$h2{$_}  } for keys %h1;  }' big_input.txt
052012 32.4666666666021
042012 31.8250000001141
042013 34.3000000000646

real    0m8.600s
user    0m8.480s
sys 0m0.032s
bash-4.3$ wc -l big_input.txt 
1100000 big_input.txt

OBSERVAÇÃO : para o formato csv, use perl -a -F',' -lne

    
por Sergiy Kolodyazhnyy 27.06.2017 / 22:35
0

Isso pode ser mais adequado ao Stack Overflow; no entanto, aqui está uma solução usando Python, na qual você deve substituir temperature_data.txt na primeira linha do seu arquivo.

f=open("temperature_data.txt","r") ### REPLACE temperature_data.txt WITH THE FILE CONTAINING YOUR DATA
flines=f.readlines() #read the file in question
f.close()

flines_split=[line.split() for line in flines] #split each line up
data_split=[line for line in flines_split if len(line)>=5 and line[4].count("/")==2] #get only lines with the date in
gathered_data={}
for line in data_split: #this block sanitises the data
    month=int(line[4][:2]) ### NOTE THAT THIS ASSUMES YOU ARE USING AMERICAN DATE FORMAT
    ### IF YOU ARE NOT, REPLACE "month=int(line[4][:2])" WITH "month=int(line[4][3:5])"
    year=int(line[4][6:])
    if (month,year) in gathered_data:
        gathered_data[(month,year)].append(float(line[5]))
    else:
        gathered_data[(month,year)]=[float(line[5])]

def mean(l): #function to calculate means
    return sum(l)/float(len(l))

means={k:mean(gathered_data[k]) for k in gathered_data} #calculate means

print("Month Year Temperature")
for k in sorted(list(means)): #print output
    print("{date[0]:^5} {date[1]} {temp:.4}".format(date=k,temp=means[k])) ### the 4 in {temp:.4} specifies precision and can be modified.
    
por Benedict Randall Shaw 03.06.2017 / 14:16