Есть ли способ получить минимум, максимум, медиану и среднее из списка чисел в одной команде?

93

У меня есть список номеров в файле, по одному в строке. Как я могу получить минимальные, максимальные, средние и средние значения? Я хочу использовать результаты в скрипте bash.

Хотя моя непосредственная ситуация с целыми числами, решение для чисел с плавающей запятой было бы полезно в дальнейшем, но простой целочисленный метод подойдет.

Peter.O
источник
stackoverflow.com/questions/3122442/…
Сиро Сантилли 新疆 改造 中心 法轮功 六四 事件

Ответы:

50

Вы можете использовать язык программирования R .

Вот быстрый и грязный R-скрипт:

#! /usr/bin/env Rscript
d<-scan("stdin", quiet=TRUE)
cat(min(d), max(d), median(d), mean(d), sep="\n")

Обратите внимание, "stdin"в scanкотором есть специальное имя файла для чтения из стандартного ввода (то есть из каналов или перенаправлений).

Теперь вы можете перенаправить ваши данные через стандартный ввод в скрипт R:

$ cat datafile
1
2
4
$ ./mmmm.r < datafile
1
4
2
2.333333

Также работает для плавающих точек:

$ cat datafile2
1.1
2.2
4.4
$ ./mmmm.r < datafile2
1.1
4.4
2.2
2.566667

Если вы не хотите писать файл сценария R, вы можете вызвать настоящую однострочную строку (с переносом строки только для удобства чтения) в командной строке, используя Rscript:

$ Rscript -e 'd<-scan("stdin", quiet=TRUE)' \
          -e 'cat(min(d), max(d), median(d), mean(d), sep="\n")' < datafile
1
4
2
2.333333

Прочитайте прекрасные руководства R на http://cran.r-project.org/manuals.html .

К сожалению, полная ссылка доступна только в формате PDF. Другой способ прочитать ссылку - набрать ?topicnameв приглашении интерактивный R-сеанс.


Для полноты: есть команда R, которая выводит все нужные вам значения и многое другое. К сожалению, в удобном для человека формате, который трудно анализировать программно.

> summary(c(1,2,4))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   1.500   2.000   2.333   3.000   4.000 
lesmana
источник
1
Это выглядит интересно ... Я посмотрю это подробнее завтра. На странице Википедии "R стал стандартом де-факто среди статистиков" ... ну, это значительная награда ... Я на самом деле пытался загрузить его на днях (я продолжал видеть, что это упомянуто), но я не мог найти это в
репозитории
10
в репозитории Ubuntu (и Debian?) пакет называется r-base.
Лесмана
спасибо, мне нужна была ссылка на это имя :) Я не думал о r- в поле поиска синапсов, и он не действует на одинокого персонажа ... Я попробовал это сейчас, и это выглядит идеально ... Rязык, безусловно, является лучшим для моих требований в этой ситуации. Согласно ответу Жиля, Rscriptинтерфейс к файлам сценариев является наиболее подходящим (по сравнению Rс интерактивным интерфейсом) ... и R в терминале делает его удобным калькулятором. или тестовая среда (например, python :)
Peter.O
(+1) Я люблю Р. Я не могу рекомендовать это достаточно.
Дейсон
6
или простоcat datafile | Rscript -e 'print(summary(scan("stdin")));'
шаббычеф
52

На самом деле я держу небольшую программу awk, чтобы получить сумму, количество данных, минимальное значение, максимальное значение, среднее значение и медиану одного столбца числовых данных (включая отрицательные числа):

#!/bin/sh
sort -n | awk '
  BEGIN {
    c = 0;
    sum = 0;
  }
  $1 ~ /^(\-)?[0-9]*(\.[0-9]*)?$/ {
    a[c++] = $1;
    sum += $1;
  }
  END {
    ave = sum / c;
    if( (c % 2) == 1 ) {
      median = a[ int(c/2) ];
    } else {
      median = ( a[c/2] + a[c/2-1] ) / 2;
    }
    OFS="\t";
    print sum, c, ave, median, a[0], a[c-1];
  }
'

Вышеприведенный скрипт читает из stdin и печатает разделенные табуляцией столбцы вывода в одну строку.

Брюс Эдигер
источник
1
Ага! это очевидно (теперь, когда я видел ваш awk-скрипт :) ... Нет необходимости постоянно проверять минимальное и максимальное значения при сортировке массива :), а это означает, что NR==1можно начинать (бесполезное использование if) вместе с проверками min / max, так что вся инициализация может быть расположена в разделе BEGIN (хорошо!) ... Допуск к комментариям тоже приятный штрих .. Спасибо, +1 ...
Peter.O
Просто мысль ... может быть, лучше использовать только цифры, чем запрещать комментарии (но это зависит от ваших требований) ..
Peter.O
1
Технически, awkпредполагается, что «новые» переменные равны нулю, поэтому в этом случае BEGIN{}раздел не нужен. Я исправил перенос (нет необходимости избегать разрывов строк). Я также использовал OFS="\t"для очистки printстроки и реализовал второй комментарий @ Peter.O. (Да, мое регулярное выражение позволяет ., но, как awkинтерпретирует это как 0, это приемлемо.)
Адам Кац
1
@AdamKatz - это большие перемены, но в настоящий момент я не писал программу. Мой awkсценарий сейчас существенно отличается. Я почти чувствую, что вы должны взять кредит для вышеупомянутой программы, чтобы дать кредит, где кредит должен.
Брюс Эдигер
1
Кстати, я написал Perl-скрипт с именем avg, который делает это и даже больше.
Адам Кац
48

С GNU datamash :

$ printf '1\n2\n4\n' | datamash max 1 min 1 mean 1 median 1
4   1   2.3333333333333 2
cuonglm
источник
4
Самый
3
brew install datamashдает вам рабочую версию для macOS, если у вас установлен Hombrew.
За Лундберг
19

Минимальное, максимальное и среднее значения довольно легко получить с помощью awk:

% echo -e '6\n2\n4\n3\n1' | awk 'NR == 1 { max=$1; min=$1; sum=0 }
   { if ($1>max) max=$1; if ($1<min) min=$1; sum+=$1;}
   END {printf "Min: %d\tMax: %d\tAverage: %f\n", min, max, sum/NR}'
Min: 1  Max: 6  Average: 3,200000

Вычислить медиану немного сложнее, так как вам нужно отсортировать числа и некоторое время сохранить их в памяти или прочитать их дважды (первый раз, чтобы посчитать их, второй - чтобы получить медиану). Вот пример, который хранит все числа в памяти:

% echo -e '6\n2\n4\n3\n1' | sort -n | awk '{arr[NR]=$1}
   END { if (NR%2==1) print arr[(NR+1)/2]; else print (arr[NR/2]+arr[NR/2+1])/2}' 
3
gelraen
источник
Спасибо ... твой пример - хороший пример для awk, для меня .. Я немного подправил его и соединил их вместе (получая ощущение awk) ... Я использовал awk asortвместо piped sortи, кажется, правильно сортирует целые и десятичные числа .. Вот ссылка на мою получившуюся версию paste.ubuntu.com/612674 ... (И примечание для Ким: я экспериментировал с awk уже пару часов Работа с примером личного интереса намного лучше для меня) ... Общее замечание для читателей: мне все еще интересно увидеть другие методы. чем компактнее, тем лучше. Я подожду
немного
17

Pythonpy хорошо работает для такого рода вещей:

cat file.txt | py --ji -l 'min(l), max(l), numpy.median(l), numpy.mean(l)'
RussellStewart
источник
17

Минимум:

jq -s min

Максимум:

jq -s max

Медиана:

sort -n|awk '{a[NR]=$0}END{print(NR%2==1)?a[int(NR/2)+1]:(a[NR/2]+a[NR/2+1])/2}'

Средний:

jq -s add/length

В jqна -s( --slurpопции) создает массив для входных линий после разбора каждой строки , как JSON, или в виде числа в данном случае.

nisetama
источник
3
Решение jq заслуживает отдельного упоминания, так как оно лаконично и переопределяет инструмент неочевидным образом.
jplindstrom
1
прекрасный! Жаль, что я мог бы дать +2
RASG
7
nums=$(<file.txt); 
list=(`for n in $nums; do printf "%015.06f\n" $n; done | sort -n`); 
echo min ${list[0]}; 
echo max ${list[${#list[*]}-1]}; 
echo median ${list[${#list[*]}/2]};
NotANumber
источник
echo file.txtвыглядит не совсем правильно, может бытьcat
малат
6

И один (длинный) лайнер Perl, включая медиану:

cat numbers.txt \
| perl -M'List::Util qw(sum max min)' -MPOSIX -0777 -a -ne 'printf "%-7s : %d\n"x4, "Min", min(@F), "Max", max(@F), "Average", sum(@F)/@F,  "Median", sum( (sort {$a<=>$b} @F)[ int( $#F/2 ), ceil( $#F/2 ) ] )/2;'

Используются специальные опции:

  • -0777 : читать весь файл сразу, а не построчно
  • -a : автоматическое разбиение на массив @F

Более читаемая версия скрипта тоже самое:

#!/usr/bin/perl

use List::Util qw(sum max min);
use POSIX;

@F=<>;

printf "%-7s : %d\n" x 4,
    "Min", min(@F),
    "Max", max(@F),
    "Average", sum(@F)/@F,
    "Median", sum( (sort {$a<=>$b} @F)[ int( $#F/2 ), ceil( $#F/2 ) ] )/2;

Если вы хотите десятичные дроби, заменить %dчто-то вроде %.2f.

mivk
источник
6

Simple-r это ответ:

r summary file.txt
r -e 'min(d); max(d); median(d); mean(d)' file.txt

Он использует среду R для упрощения статистического анализа.

user48270
источник
5

Просто для того, чтобы на этой странице были представлены различные варианты, вот еще два способа:

1: октава

  • GNU Octave - это интерпретируемый язык высокого уровня, в первую очередь предназначенный для численных расчетов. Он предоставляет возможности для численного решения линейных и нелинейных задач, а также для выполнения других численных экспериментов.

Вот быстрый пример октавы.

octave -q --eval 'A=1:10;
  printf ("# %f\t%f\t%f\t%f\n", min(A), max(A), median(A), mean(A));'  
# 1.000000        10.000000       5.500000        5.500000

2: bash + специализированные инструменты .

Для bash для обработки чисел с плавающей точкой этот скрипт использует numprocessи numaverageиз пакета num-utils.

PS. У меня также был разумный взгляд bc, но для этой конкретной работы она не предлагает ничего кроме того, что awkделает. Это (как указывает 'c' в 'bc') калькулятор - калькулятор, который требует много программирования, как awkи этот скрипт bash ...


arr=($(sort -n "LIST" |tee >(numaverage 2>/dev/null >stats.avg) ))
cnt=${#arr[@]}; ((cnt==0)) && { echo -e "0\t0\t0\t0\t0"; exit; }
mid=$((cnt/2)); 
if [[ ${cnt#${cnt%?}} == [02468] ]] 
   then med=$( echo -n "${arr[mid-1]}" |numprocess /+${arr[mid]},%2/ )
   else med=${arr[mid]}; 
fi     #  count   min       max           median        average
echo -ne "$cnt\t${arr[0]}\t${arr[cnt-1]}\t$med\t"; cat stats.avg 
Peter.O
источник
4

Я буду вторым выбором R Лесманы и предложу свою первую программу R. Он читает одно число в строке на стандартном вводе и записывает четыре числа (мин, макс, среднее, медиана), разделенных пробелами, в стандартный вывод.

#!/usr/bin/env Rscript
a <- scan(file("stdin"), c(0), quiet=TRUE);
cat(min(a), max(a), mean(a), median(a), "\n");
жилль
источник
Спасибо за "второе" (это обнадеживает) ... ваш пример был полезен, так как я не сразу понял, что Rэто интерактивный интерфейс и Rscriptуправляет скриптовыми файлами, которые могут быть исполняемыми в соответствии с вашим примером hash-bang , или вызывается из скрипта bash. Скрипты могут обрабатывать аргументы командной строки (например, stackoverflow.com/questions/2045706/… ), поэтому это выглядит хорошо ... Также выражения R могут использоваться в bash через -e... but Мне интересно, как это Rсравнивать с bc...
Peter.O
2

Ниже sort/ awkтандем это делает:

sort -n | awk '{a[i++]=$0;s+=$0}END{print a[0],a[i-1],(a[int(i/2)]+a[int((i-1)/2)])/2,s/i}'

(он рассчитывает медиану как среднее из двух центральных значений, если количество значений четное)

МИК
источник
2

Если взять реплики из кода Брюса, то здесь есть более эффективная реализация, которая не хранит все данные в памяти. Как указано в вопросе, предполагается, что входной файл имеет (самое большее) одно число на строку. Он считает строки во входном файле, которые содержат квалифицирующий номер, и передает счет awkкоманде вместе с (предшествующими) отсортированным данным. Так, например, если файл содержит

6.0
4.2
8.3
9.5
1.7

тогда вход на awkсамом деле

5
1.7
4.2
6.0
8.3
9.5

Затем awkскрипт захватывает количество данных в NR==1блоке кода и сохраняет среднее значение (или два средних значения, которые усредняются для получения медианы), когда он их видит.

FILENAME="Salaries.csv"

(awk 'BEGIN {c=0} $1 ~ /^[-0-9]*(\.[0-9]*)?$/ {c=c+1;} END {print c;}' "$FILENAME"; \
        sort -n "$FILENAME") | awk '
  BEGIN {
    c = 0
    sum = 0
    med1_loc = 0
    med2_loc = 0
    med1_val = 0
    med2_val = 0
    min = 0
    max = 0
  }

  NR==1 {
    LINES = $1
    # We check whether numlines is even or odd so that we keep only
    # the locations in the array where the median might be.
    if (LINES%2==0) {med1_loc = LINES/2-1; med2_loc = med1_loc+1;}
    if (LINES%2!=0) {med1_loc = med2_loc = (LINES-1)/2;}
  }

  $1 ~ /^[-0-9]*(\.[0-9]*)?$/  &&  NR!=1 {
    # setting min value
    if (c==0) {min = $1;}
    # middle two values in array
    if (c==med1_loc) {med1_val = $1;}
    if (c==med2_loc) {med2_val = $1;}
    c++
    sum += $1
    max = $1
  }
  END {
    ave = sum / c
    median = (med1_val + med2_val ) / 2
    print "sum:" sum
    print "count:" c
    print "mean:" ave
    print "median:" median
    print "min:" min
    print "max:" max
  }
'
Рахул Агарвал
источник
Добро пожаловать в Unix и Linux! Хорошая работа для первого поста. (1) Хотя это может ответить на вопрос, было бы лучше ответить, если бы вы могли объяснить, как и почему это происходит. Стандарты сайта развивались в течение последних четырех лет; в то время как в 2011 году были приемлемы только ответы с кодом, сейчас мы предпочитаем подробные ответы, которые дают больше объяснений и контекста Я не прошу вас объяснить весь сценарий; только части, которые вы изменили (но если вы хотите объяснить весь сценарий, это тоже нормально). (Кстати, я прекрасно понимаю; я спрашиваю от имени наших менее опытных пользователей.)… (Продолжение)
G-Man
(Продолжение)… Пожалуйста, не отвечайте в комментариях; отредактируйте свой ответ, чтобы сделать его более понятным и полным. (2) Исправление сценария так, что ему не нужно хранить весь массив в памяти, является хорошим улучшением, но я не уверен, уместно ли говорить, что ваша версия «более эффективна», когда у вас есть три ненужные catкоманды; см UUOC . … (Продолжение)
G-Man
(Продолжение)… (3) Ваш код безопасен, так как вы устанавливаете FILENAMEи знаете, что вы его устанавливаете, но, в общем, вы всегда должны заключать в кавычки переменные оболочки, если у вас нет веских причин не делать этого, и вы уверен, что ты знаешь, что делаешь. (4) И ваш ответ, и Брюс игнорируют отрицательный ввод (т. Е. Числа, начинающиеся с -); в этом вопросе нет ничего, что могло бы предположить, что это правильное или желаемое поведение. Не плохо себя чувствую; Прошло более четырех лет, и, видимо, я первый человек, который заметил.
G-Man
Внесены изменения согласно предложениям. Не знал о накладных расходах команды cat. Всегда использовал его для потоковой передачи отдельных файлов. Спасибо, что рассказали мне о UUOC .....
Рахул Агарвал
Хороший. Я устранил третий catи добавил к объяснению.
G-Man
2

Это numкрошечная awkобертка, которая именно это и делает, например,

$ echo "1 2 3 4 5 6 7 8 9" | num max
9
$ echo "1 2 3 4 5 6 7 8 9" | num min max median mean
..and so on

это спасает вас от изобретения колеса в ультрапортативном awk. Документы приведены выше, а прямая ссылка здесь (см. Также страницу GitHub ).

coderofsalvation
источник
Ссылки на скрытый веб-код, выполняемый на компьютере пользователя, кажутся мне плохой идеей. Сайт , который содержит код находится здесь
Где был этот «проверенный битвой» код перед тем, как его поместили на github всего 4 месяца назад? Я нахожу крайне подозрительным, что ссылка на github должна быть очищена командой curl download. Гораздо проще узнать, как пожертвовать деньги разработчику. Похоже, автор этого кода боится, что люди могут пойти на github и посмотреть (почти не существующую) историю и статистику. Есть ли какая-либо причина, чтобы назвать эту битву проверенной вообще, кроме попытки собрать деньги?
Anthon
@BinaryZeba: обновлено
coderofsalvation
@Anthon хорошо, убрал часть, прошедшую тестирование. Я не думаю, что это место для заговора FUD.
кодер-спасение
2

С perl:

$ printf '%s\n' 1 2 4 |
   perl -MList::Util=min,max -MStatistics::Basic=mean,median -w -le '
     chomp(@l = <>); print for min(@l), max(@l), mean(@l), median(@l)'
1
4
2.33
2
Стефан Шазелас
источник
1

cat/pythonЕдинственное решение - не пустое доказательство ввода!

cat data |  python3 -c "import fileinput as FI,statistics as STAT; i = [int(l) for l in FI.input()]; print('min:', min(i), ' max: ', max(i), ' avg: ', STAT.mean(i), ' median: ', STAT.median(i))"
ravwojdyla
источник
Вы не показали медиану
Peter.O
@ Peter.O исправлено.
ravwojdyla
Модуль статистики требует версию Python> = 3.4
Peter.O
@ Peter.O вы правы - это проблема?
ravwojdyla
Это не проблема, если у вас нет подходящей версии Python. Это просто делает его менее портативным.
Peter.O
0

Если вы больше заинтересованы в полезности, чем в том, чтобы быть крутым или умным, тогда perlэто более простой выбор, чем awk. По большому счету он будет на каждом * nix с единообразным поведением, и его легко и бесплатно установить на windows. Я думаю, что это также менее загадочно awk, и будут некоторые статистические модули, которые вы могли бы использовать, если бы вы хотели на полпути между написанием этого сами и чем-то вроде R. Мой довольно непроверенный (на самом деле я знаю, что есть ошибки, но это работает для моих целей ) perlсценарий занял около минуты, и я предполагаю, что единственная загадочная часть while(<>)- это очень полезное сокращение, означающее, что файл (ы) передается в качестве аргументов командной строки, читается строка за раз и помещается эта строка в специальной переменной$_, Таким образом, вы можете поместить это в файл с именем count.pl и запустить его как perl count.pl myfile. Кроме того, должно быть до боли очевидно, что происходит.

$max = 0;
while (<>) {
 $sum = $sum + $_;
 $max = $_ if ($_ > $max);
 $count++;
}
$avg=$sum/$count;
print "$count numbers total=$sum max=$max mean=$avg\n";
Iain
источник
3
Вы не показали медиану
Peter.O
0
function median()
{
    declare -a nums=($(cat))
    printf '%s\n' "${nums[@]}" | sort -n | tail -n $((${#nums[@]} / 2 + 1)) | head -n 1
}  
Дэвид Маклафлин
источник
Этот ответ был бы полезен, если бы было объяснение того, как приведенный выше код отвечает на вопрос, например, вы должны сказать, что он использует Bash (не sh) в качестве интерпретатора. Также существует проблема с тем, как данные считываются в массив из файла.
Энтони