it-swarm-ru.tech

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

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

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

101
Peter.O

Вы можете использовать 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 
54
lesmana

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

55
Bruce Ediger

С GNU datamash :

$ printf '1\n2\n4\n' | datamash max 1 min 1 mean 1 median 1
4   1   2.3333333333333 2
53
cuonglm

Минимальное, максимальное и среднее значения довольно легко получить с помощью 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
20
gelraen

Минимум:

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 the -s (--Slurp) опция создает массив для строк ввода после разбора каждой строки как JSON или как числа в этом случае.

20
nisetama

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

cat file.txt | py --ji -l 'min(l), max(l), numpy.median(l), numpy.mean(l)'
18
RussellStewart

И один (длинный) лайнер 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.

7
mivk
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]};
7
NotANumber

Simple-r ответ:

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

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

6
user48270

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

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 
5
Peter.O

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

#!/usr/bin/env Rscript
a <- scan(file("stdin"), c(0), quiet=TRUE);
cat(min(a), max(a), mean(a), median(a), "\n");
4

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 ).

3
coderofsalvation

Следующий тандем 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
mik

Если взять реплики из кода Брюса, то здесь есть более эффективная реализация, которая не хранит все данные в памяти. Как указано в вопросе, предполагается, что входной файл имеет (самое большее) одно число в строке. Он подсчитывает строки во входном файле, которые содержат квалифицирующий номер, и передает их команде 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
  }
'
2
Rahul Agarwal

С 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
2
Stéphane Chazelas

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))"
1
ravwojdyla
function median()
{
    declare -a nums=($(cat))
    printf '%s\n' "${nums[@]}" | sort -n | tail -n $((${#nums[@]} / 2 + 1)) | head -n 1
}  
0
David McLaughlin

Если вас больше интересует утилита, а не то, что вы крутой или умный, тогда Perl проще, чем awk. По большому счету он будет на каждом * nix с единообразным поведением, и его легко и бесплатно установить на windows. Я думаю, что это также менее загадочно, чем awk, и будут некоторые статистические модули, которые вы могли бы использовать, если бы хотели на полпути между написанием этого сами и чем-то вроде R. Мой довольно непроверенный (на самом деле я знаю, что в нем есть ошибки но это работает для моих целей) Написание сценария Perl заняло около минуты, и я предполагаю, что единственной загадочной частью будет while(<>), что является очень полезным сокращением, то есть take файл (ы) передаются в качестве аргументов командной строки, одновременно считывают строку и помещают эту строку в специальную переменную $_. Таким образом, вы можете поместить это в файл с именем 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";
0
iain