Как рассчитать среднее значение конкретного месяца столбца с годами?

У меня есть текстовый файл с данными о температуре за апрель и май за шесть лет. Я хочу рассчитать среднее значение каждого месяца с каждым годом. Я использую команду awk, но она вычисляет среднее значение температуры. Я не знаю, как использовать команду awk для этой проблемы.

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

Образец файла, который я показываю,

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

5 ответов

Другое - очень гибкое - решение Python на основе itertools.groupby: https://github.com/davidfoerster/group-aggregate

Монтаж

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

использование

./group-aggregate.py [--skip N] [options...] groups aggregators...
  • groups - Список индексов полей или диапазонов столбцов, используемых для группировки записей (начиная с нуля, через запятую).

  • aggregators - Индекс поля (начиная с нуля) или диапазон столбцов, имя функции агрегирования и, необязательно, строка формата, все разделенные двоеточиями.

  • --skip N - Пропустить N строк в начале ввода (например, строки заголовка).

Смотрите вывод python3 -O group-aggregate.py --help для большего.

Примеры

Пример 1

Программа группировки и агрегирования не может обрабатывать частичные поля; давайте переформатируем ваш набор данных с помощью других инструментов, чтобы обойти его:

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

Теперь поле группировки, год, имеет индекс 6, а агрегированное поле, температуры, имеет индекс 7, из которого вы хотите взять среднее значение:

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

Вы также можете отформатировать средние значения температуры, в этом примере, чтобы показать ровно одно десятичное место:

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

Пример 2

Вместо разделителей полей вы также можете указать диапазоны столбцов, которые хорошо работают с вашим форматом данных:

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

Теперь вам даже не нужно предварительно форматировать данные, как в примере 1.

Выход

Вывод обеих примеров команд одинаков:

2012    32.2
2013    34.3

Вы можете сделать это с помощью небольшого скрипта 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])))

Он читает файл, указанный в качестве аргумента при выполнении скрипта построчно, и создает словарь, который отображает годы в списки значений температуры. После обработки всего файла он рассчитает и напечатает средние температуры за год.

Вот пример запуска с файлом данных vk4.txt Вы предоставили. Я сохранил сценарий выше как avgtemp.py в текущем каталоге и сделал его исполняемым с помощью chmod +x avgtemp.py:

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

Если вы хотите, точный формат вывода может быть легко изменен простым редактированием "{0}:\t{1:.2f}" строка формата в последней строке скрипта. Здесь вы можете ввести любой шаблон, если он содержит {0} заменить на год и {1:.2f} или подобный, чтобы быть замененным средней температурой, показанной с двумя десятичными цифрами. \t это вкладка

Основная идея будет состоять в том, чтобы создать ключ год-месяц из поля даты, а затем суммировать и подсчитывать записи на основе этого ключа, используя ассоциативные массивы, например

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

Тестирование с вашими данными:

$ 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

Если у вас есть GNU awk (gawk) v4 + вы можете добавить явную сортировку.

Это может быть более подходящим для переполнения стека; Тем не менее, вот решение с использованием Python, в котором вы должны заменить temperature_data.txt в первой строке с вашим файлом.

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.

Perl решение

Вот команда с одной строкой, которая работает при условии построения двух хешей: $h1 для суммирования значений температуры и $h2 для хранения общего количества обработанных записей. Каждый соответствующий элемент будет содержать один и тот же ключ в формате MMYYYY который извлекается из вашего столбца № 5 (который предназначен для perl индекс массива № 4, т.е. $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;  }'

Ключевой момент, чтобы отметить здесь:

  • мы используем do {} if condition1 and condition2 состав. {} действие выполняется только тогда, когда номер строки не равен 1 (т. е. мы пропускаем заголовок) и существует $F[4] (т.е. мы избегаем пустых или неполных строк).

  • @a=split "/",$F[4] позволяет нам сломаться MM/DD/YYYY дата штамп на части и с $k= $a[0] . $a[2] мы создаем ключевую переменную, которая позволит нам хранить данные в двух хешах.

  • END{} структура будет выполнять действие, когда весь файл будет прочитан.


Решение работает достаточно хорошо. Вот тест с 1100 000 строк ввода:

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

ПРИМЕЧАНИЕ: для использования в формате CSV perl -a -F',' -lne вместо

Другие вопросы по тегам