1 votes

Comment calculer la moyenne d'une colonne particulière mois par mois avec les années?

J'ai un fichier texte contenant des données de température des mois d'avril et de mai pour six ans. Je veux calculer la moyenne de chaque mois pour chaque année. J'utilise la commande awk mais elle calcule la moyenne de température globale. Je ne sais pas comment utiliser la commande awk pour ce problème.

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

Le fichier d'exemple que je montre,

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

2voto

David Foerster Points 34353

Une autre solution Python très flexible basée sur itertools.groupby: https://github.com/davidfoerster/group-aggregate

Installation

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

Utilisation

./group-aggregate.py [--skip N] [options...] groups aggregators...
  • groupes - Une liste d'index de champ ou de plages de colonnes utilisée pour regrouper des enregistrements (à partir de zéro, séparés par des virgules).

  • agrégateurs - Un index de champ (à partir de zéro) ou une plage de colonnes, le nom d'une fonction d'agrégation et éventuellement une chaîne de format, le tout séparé par des deux-points.

  • --skip N - Ignorer N lignes au début de l'entrée (par exemple, les lignes d'en-tête).

Voir la sortie de python3 -O group-aggregate.py --help pour plus d'informations.

Exemples

Exemple 1

Le programme de regroupement et d'agrégation ne peut pas gérer les champs partiels ; reformatez votre jeu de données avec d'autres outils pour contourner ce problème :

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

Maintenant, le champ de regroupement, l'année, a l'index 6 et le champ agrégé, les températures, a l'index 7 dont vous souhaitez calculer la moyenne :

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

Vous pouvez également formater les moyennes de température, dans cet exemple pour afficher exactement une décimale :

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

Exemple 2

Au lieu des séparateurs de champs, vous pouvez également spécifier des plages de colonnes qui fonctionnent bien avec le format de vos données :

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

Maintenant, vous n'avez même pas besoin de pré-formater les données comme dans l'exemple 1.

Sortie

La sortie des deux commandes d'exemple est la même :

2012    32.2
2013    34.3

1voto

Byte Commander Points 99026

Vous pourriez le faire avec un petit script Python :

#!/usr/bin/env python3

import sys
if len(sys.argv) != 2:
    print("Vous devez fournir exactement un nom de fichier à lire en argument.")
    exit(-1)

file = open(sys.argv[1])
file.readline()  # pour enlever l'entête

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

Il lit le fichier spécifié comme argument lors de l'exécution du script ligne par ligne et crée un dictionnaire qui mappe les années à des listes de valeurs de température. Après le traitement de l'ensemble du fichier, il calculera et affichera les températures moyennes par année.

Voici un exemple d'exécution avec le fichier de données vk4.txt que vous avez fourni. J'ai enregistré le script ci-dessus sous le nom avgtemp.py dans le répertoire actuel et je l'ai rendu exécutable en utilisant chmod +x avgtemp.py:

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

Si vous le souhaitez, le format de sortie exact pourrait être facilement modifié en éditant simplement la chaîne de format "{0}:\t{1:.2f}" dans la dernière ligne du script. Vous pouvez entrer n'importe quel motif ici, tant qu'il contient un {0} à remplacer par l'année et {1:.2f} ou similaire à remplacer par la température moyenne, affichée avec deux chiffres après la virgule. Le \t est une tabulation.

1voto

steeldriver Points 118154

L'idée de base sera de créer une clé année-mois à partir du champ de date, puis de sommer et compter les entrées en fonction de cette clé en utilisant des tableaux associatifs par exemple.

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

Testez avec vos données:

$ 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

Si vous avez GNU awk (gawk) v4+, vous pouvez ajouter un tri explicite.

0voto

Cela pourrait être plus approprié pour Stack Overflow; cependant, voici une solution en utilisant Python, dans laquelle vous devez remplacer temperature_data.txt dans la première ligne par votre fichier.

f=open("temperature_data.txt","r") ### REMPLACER temperature_data.txt AVEC LE FICHIER CONTENANT VOS DONNÉES
flines=f.readlines() #lire le fichier en question
f.close()

flines_split=[line.split() for line in flines] #diviser chaque ligne
data_split=[line for line in flines_split if len(line)>=5 and line[4].count("/")==2] #obtenir seulement les lignes avec la date
gathered_data={}
for line in data_split: #ce bloc assainit les données
    month=int(line[4][:2]) ### NOTEZ QUE CELA SUPPOSE QUE VOUS UTILISEZ LE FORMAT DE DATE AMÉRICAIN
    ### SI CE N'EST PAS LE CAS, REMPLACEZ "month=int(line[4][:2])" PAR "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): #fonction pour calculer les moyennes
    return sum(l)/float(len(l))

means={k:mean(gathered_data[k]) for k in gathered_data} #calculer les moyennes

print("Month Year Temperature")
for k in sorted(list(means)): #afficher le résultat
    print("{date[0]:^5} {date[1]} {temp:.4}".format(date=k,temp=means[k])) ### le 4 dans {temp:.4} spécifie la précision et peut être modifié.

0voto

Sergiy Kolodyazhnyy Points 97292

Solution Perl

Voici une commande d'une seule ligne, qui repose sur le principe de construction de deux hachages - $h1 pour la somme des valeurs de température et $h2 pour stocker le nombre total d'enregistrements traités. Chaque hachage correspondant contiendra la même clé au format MMYYYY qui est extraite de votre colonne n°5 (correspondant à l'index #4 du tableau perl, c'est-à-dire $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;  }'

Point clé à noter ici :

  • nous utilisons la structure do {} if condition1 et condition2. L'action {} est effectuée uniquement lorsque le numéro de ligne n'est pas 1 (c'est-à-dire nous sautons l'en-tête) et qu'il y a $F[4] (c'est-à-dire nous évitons les lignes vides ou incomplètes).

  • @a=split "/",$F[4] nous permet de découper le tampon de date MM/JJ/AAAA en parties et avec $k= $a[0] . $a[2] nous créons une variable clé qui nous permettra de stocker les données dans deux hachages.

  • La structure END{} effectuera l'action lorsque le fichier entier aura été lu.


La solution fonctionne raisonnablement bien. Voici un test avec 1 100 000 lignes d'entrée :

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

REMARQUE : pour le format csv, utilisez perl -a -F',' -lne à la place

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X