Я хотел бы получить медианное значение из второго столбца этого CSV-файла, сгруппированного по континентам, в файле awk сценария:
Continent, Ratio
Asia,4.313743132
Europe,1.275057509
Africa,2.587215976
Europe,0.382270638
Africa,1.915438434
Latin America and the Caribbean,1.801681569
Latin America and the Caribbean,1.416472111
Asia,2.039169471
Latin America and the Caribbean,0.622595518
Я пытался, но это не работает, потому что я не сортировал CSV и даже не группировал по континентам:
BEGIN {
FS = ","
Continent["Africa"];Continent["Asia"];Continent["Europe"];Continent["Latin America and the Caribbean"];
}
FNR>1 {
if ($1! = "" && $2! = "") {
a[i++]=$2
}
}END {
for (i in Continent) {
x=int((i+1)/2); if (x < (i+1)/2) print (a[x-1]+a[x])/2; else print a[x-1];
}
}
Однако я рассчитываю заказать в первую очередь csv по континентам и цифрам (по убыванию или по возрастанию, без разницы):
Continent, Ratio
Africa,2.587215976
Africa,1.915438434
Asia,4.313743132
Asia,2.039169471
Europe,1.275057509
Europe,0.382270638
Latin America and the Caribbean,1.801681569
Latin America and the Caribbean,1.416472111
Latin America and the Caribbean,0.622595518
И, наконец, получение медианы по континентам:
Continent, Median
Africa 2,2513
Asia 3,1764
Europe 0,8286
Latin America and the Caribbean, 1.4164
Всего 246 строк, да, легко помещается в памяти!
В вашем файле есть строка заголовка? Вы не показываете его, но FNR>1 предполагает, что он есть.
Полностью, ты прав. Да, есть! Обновлено.
Или используя GNU datamash (Отказ от ответственности: я участник): datamash -H -s -t, groupby 1 median 2 < file.csv
Как это обман, когда данные OP не в правильном порядке?
@anubhava Ответ в связанных вопросах и ответах сортирует входные данные (OP необходимо будет настроить его для сортировки первого столбца в алфавитном порядке, а не в числовом, а также исправить использование ", " в качестве входных и выходных разделителей)
Спасибо @Shawn, я думаю, что отличий немного, и это можно сделать одной командой вместо двух.
@anubhava Тогда добавь ответ на этот вопрос.
ИМО, этот вопрос должен быть закрыт за отсутствие попытки решить проблему.
Используя gnu-awk, вы можете использовать это решение:
cat med.awk
function med(d) {
return gensub(/\./, ",", "1", sprintf("%.4f",
(n%2 ? d[(n+1)/2] : (d[n/2] + d[n/2+1]) / 2)))
}
BEGIN {
FS = ","
OFS = "\t"
}
NR > 1 {
a[$0] = $2
}
END {
PROCINFO["sorted_in"] = "@ind_num_asc" # this is for sorting
print "Continent", "Median
for (i in a) {
k = gensub(/,.*/, "", "1", i)
if (k != pk) {
if (pk)
print pk, med(d)
n = 0
delete d
}
d[++n] = a[i]
pk = k
}
print pk, med(d)
}
Затем используйте его как:
awk -f med.awk file | column -t -s $'\t'
Continent Median
Africa 2,2513
Asia 3,1765
Europe 0,8287
Latin America and the Caribbean 1,4165
Спасибо @anubhava, успехов! Ключ - функция gensub, между прочим, я раньше не встречал! Браво!
Хм, удивлен, что одно решение awk было недостаточно хорошим по сравнению с решением, которое вызывает внешнюю sort утилиту.
Ради интереса можно написать это в одном gawk скрипте, но в координации с sort, используя двухсторонний канал (или сопроцесс):
#!/usr/bin/env gawk -f
function median(values, _, len) {
len = length(values)
if (len % 2 == 1) {
return values[(len + 1) / 2]
} else {
return (values[int(len / 2)] + values[int(len / 2) + 1]) / 2
}
}
BEGIN {
# Set (Input) Field and Output Field Separators.
FS = OFS = ","
# The `sort` process to which we'll send data.
#
# -t, → set comma (,) to be the input separator
# -k1,1 → 1st sort key starts at the 1st field and ends at the 1st field
# -k2,2n → 2nd sort key starts at the 2nd field and ends at the 2nd field
# and use numeric comparisons.
sorted = "LC_ALL=C sort -t, -k1,1 -k2,2n"
}
NR == 1 {
next # skip first line
}
{
# Pipe incoming lines to a sorting coprocess.
# See: https://www.gnu.org/software/gawk/manual/html_node/Two_002dway-I_002fO.html
print $0 |& sorted
}
END {
# Close the writing end of the `sorted` pipe.
close(sorted, "to")
while (1) {
# Read sorted data.
more = sorted |& getline
# Print median when there's no more data or when we're switching between regions.
if (!more || (region && region != $1)) {
print region, sprintf("%.6f", median(vals))
vals_i = 0
delete vals
}
# Clean up when there's no more data.
if (!more) {
close(sorted)
break
}
region = $1
vals[++vals_i] = $2
}
}
$ ./median.awk data.txt
Africa,2.251327
Asia,3.176456
Europe,0.828664
Latin America and the Caribbean,1.416472
Насколько велик CSV? (т.е. легко помещается в памяти?