Как рассчитать среднее значение конкретного месяца столбца с годами?
У меня есть текстовый файл с данными о температуре за апрель и май за шесть лет. Я хочу рассчитать среднее значение каждого месяца с каждым годом. Я использую команду 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
вместо