У меня есть текстовый файл, имеющий температурные данные месяцев в апреле и мае в течение шести лет. Я хочу вычислить средний из каждого месяца с каждым годом. Я использую команду 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
Вы могли сделать это с небольшим сценарием 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}"
строка формата в последней строке сценария. Можно ввести любой шаблон здесь, пока он содержит a {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 + можно добавить явную сортировку.
Вот короткая команда, которая воздействует на предпосылку создания двух хешей - $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{}
структура выполнит действие, когда целый файл будет считан.
Решение работает обоснованно хорошо. Вот тест с 1 100 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
вместо этого
Это могло бы больше подходить для Переполнения стека; однако, вот решение с помощью 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.
Другой – очень гибкий – решение 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
для больше.
Программа группировки и агрегирования не может обработать частичные поля; давайте переформатируем Ваш набор данных с другими инструментами для работы вокруг этого:
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
Вместо разделителей полей можно также указать диапазоны столбца, который работает хорошо с форматом данных:
./group-aggregate.py --skip 1 54-58 60-:favg:.1f < data.csv
Теперь Вы не должны даже предварительно форматировать данные как в примере 1.
Вывод обеих команд в качестве примера является тем же:
2012 32.2
2013 34.3