Czy istnieje sposób uzyskania wartości minimalnej, maksymalnej, mediany i średniej listy liczb w jednym poleceniu?

93

Mam listę liczb w pliku, po jednym w wierszu. Jak mogę uzyskać wartości minimalną, maksymalną, medianę i średnią ? Chcę użyć wyników w skrypcie bash.

Chociaż moja bezpośrednia sytuacja dotyczy liczb całkowitych, rozwiązanie dla liczb zmiennoprzecinkowych byłoby przydatne wzdłuż linii, ale prosta metoda na liczbach całkowitych jest w porządku.

Peter.O
źródło
stackoverflow.com/questions/3122442/...
Ciro Santilli 21 改造 中心 法轮功 六四 事件

Odpowiedzi:

50

Można użyć języka programowania R .

Oto szybki i brudny skrypt R:

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

Uwaga: "stdin"w scanktórej znajduje się specjalna nazwa pliku do odczytu ze standardowego wejścia (to znaczy z potoków lub przekierowań).

Teraz możesz przekierować swoje dane przez stdin do skryptu R.

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

Działa również dla zmiennoprzecinkowych:

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

Jeśli nie chcesz pisać pliku skryptu R, możesz wywołać prawdziwą jednowierszową (z podziałką tylko dla czytelności) w wierszu poleceń, używając 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

Przeczytaj dokładne instrukcje R na stronie http://cran.r-project.org/manuals.html .

Niestety pełne odniesienie jest dostępne tylko w formacie PDF. Innym sposobem na odczytanie referencji jest wpisanie ?topicnamepytania w interaktywnej sesji R.


Dla kompletności: istnieje polecenie R, które wyświetla wszystkie potrzebne wartości i więcej. Niestety w przyjaznym dla człowieka formacie, który jest trudny do analizowania programowo.

> 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
źródło
1
Wygląda interesująco. Przyjrzę się temu jutro. Na podstawie strony wikipedii „R stał się de facto standardem wśród statystyk”… cóż, to znaczące wyróżnienie… Próbowałem go pobrać
któregoś
10
w repozytorium ubuntu (i debian?) nazwa pakietu r-base.
lesmana
dzięki, potrzebowałem tego odniesienia do nazwy :) Nie myślałem o r- w polu wyszukiwania synaptycznego i nie działa on na samotną postać ... Wypróbowałem to teraz i wygląda idealnie .. Rjęzyk jest zdecydowanie najlepszy dla moich wymagań w tej sytuacji. Zgodnie z odpowiedzią Gillesa Rscriptinterfejs do plików skryptowych jest najbardziej odpowiedni (w przeciwieństwie Rdo interfejsu interaktywnego) ... a R w terminalu stanowi przydatny kalkulator lub środowisko testowe (takie jak python :)
Peter.O
(+1) Kocham R. Nie mogę tego wystarczająco polecić.
Dason
6
lub po prostucat datafile | Rscript -e 'print(summary(scan("stdin")));'
shabbychef
52

Właściwie trzymam mały program awk, aby podać sumę, liczbę danych, minimalny układ odniesienia, maksymalny układ odniesienia, średnią i medianę pojedynczej kolumny danych liczbowych (w tym liczb ujemnych):

#!/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];
  }
'

Powyższy skrypt czyta ze standardowego wejścia i wypisuje rozdzielone tabulatorami kolumny wyników w jednym wierszu.

Bruce Ediger
źródło
1
Aha! to oczywiste (teraz, gdy widziałem twój skrypt awk :) ... Nie ma potrzeby ciągłego sprawdzania wartości min i max, gdy tablica jest posortowana :) a to oznacza, że NR==1można przejść (bezużyteczne użycie if) wraz z kontrolami min / max, więc cała inicjalizacja może być zlokalizowana w sekcji BEGIN (dobrze!) ... Pozwolenie na komentarze też jest miłym akcentem. Dzięki, +1 ...
Peter.O
Tylko myśl… być może dopuszczenie tylko liczb jest lepsze niż nie zezwalanie na komentarze (ale to zależy od twoich wymagań) ..
Peter.O
1
Technicznie awkzakłada , że „nowe” zmienne są zerowe, więc w tym przypadku BEGIN{}sekcja nie jest potrzebna. Naprawiłem zawijanie (nie ma też potrzeby ucieczki przed łamaniem linii). Ja również OFS="\t"wyczyściłem printlinię i zaimplementowałem drugi komentarz @ Peter.O. (Tak, moje wyrażenie regularne na to pozwala ., ale co awkinterpretuje to jako 0akceptowalne.)
Adam Katz
1
@AdamKatz - to świetne zmiany, ale w obecnej formie nie napisałem programu. Mój awkskrypt jest teraz zupełnie inny. Wydaje mi się, że powinieneś wziąć kredyt na powyższy program, aby przyznać kredyt tam, gdzie jest on należny.
Bruce Ediger,
1
Nawiasem mówiąc, napisałem skrypt perla o nazwie avg, który robi to i więcej.
Adam Katz
47

Z GNU datamash :

$ printf '1\n2\n4\n' | datamash max 1 min 1 mean 1 median 1
4   1   2.3333333333333 2
Cuonglm
źródło
4
najprostsza jak dotąd odpowiedź na bash, jak pytano
rfabbri 27.07.16
3
brew install datamashdaje działającą wersję dla systemu macOS, jeśli masz zainstalowany Hombrew.
Per Lundberg,
19

Z awk można łatwo uzyskać min, maks i średnią:

% 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

Obliczanie mediany jest nieco trudniejsze, ponieważ musisz sortować liczby i przechowywać je wszystkie w pamięci przez chwilę lub przeczytać je dwa razy (pierwszy raz, aby je policzyć, drugi - aby uzyskać wartość mediany). Oto przykład, który przechowuje wszystkie liczby w pamięci:

% 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
źródło
Dzięki ... twój przykład jest dobrym wprowadzeniem do awk, dla mnie .. Ulepszyłem go trochę i poskładałem razem (uzyskując wrażenie awk) ... Użyłem awk asortzamiast piped sort, i wydaje się, że poprawnie sortuje liczby całkowite i dziesiętne. Oto link do mojej wynikowej wersji paste.ubuntu.com/612674 ... (I uwaga dla Kim: eksperymentuję z awk od kilku godzin Praca z przykładem interesu osobistego jest dla mnie znacznie lepsza) ... Ogólna uwaga dla czytelników: Nadal jestem zainteresowany innymi metodami. im bardziej kompaktowy, tym lepiej. Zaczekam chwilę ...
Peter.O
17

Pythonpy działa dobrze dla tego rodzaju rzeczy:

cat file.txt | py --ji -l 'min(l), max(l), numpy.median(l), numpy.mean(l)'
RussellStewart
źródło
17

Minimum:

jq -s min

Maksymalny:

jq -s max

Mediana:

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

Średni:

jq -s add/length

W jqopcji -s( --slurp) tworzy tablicę dla linii wejściowych po parsowaniu każdej linii jako JSON lub w tym przypadku jako liczbę.

nisetama
źródło
3
Rozwiązanie jq zasługuje na szczególną uwagę, ponieważ jest zwięzłe i zmienia przeznaczenie narzędzia w nieoczywisty sposób.
jplindstrom
1
piękny! chciałbym dać +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
źródło
echo file.txtnie wygląda całkiem dobrze, możecat
Malát
6

I jeden (długi) liniowiec Perla, w tym mediana:

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;'

Zastosowano specjalne opcje:

  • -0777 : czytaj cały plik na raz zamiast linii po linii
  • -a : autosplit do tablicy @F

Bardziej czytelną wersją skryptu tego samego byłoby:

#!/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;

Jeśli chcesz miejsc po przecinku, zamień na %dcoś podobnego %.2f.

mivk
źródło
6

Prosta r jest odpowiedzią:

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

Wykorzystuje środowisko R w celu uproszczenia analizy statystycznej.

użytkownik48270
źródło
5

Ze względu na różnorodność opcji prezentowanych na tej stronie, oto jeszcze dwa sposoby:

1: oktawa

  • GNU Octave to język interpretowany na wysokim poziomie, przeznaczony głównie do obliczeń numerycznych. Zapewnia możliwości numerycznego rozwiązywania problemów liniowych i nieliniowych oraz wykonywania innych eksperymentów numerycznych.

Oto przykład szybkiej oktawy.

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 + narzędzia jednofunkcyjne .

Aby bash obsługiwał liczby zmiennoprzecinkowe, ten skrypt używa numprocessi numaveragez pakietu num-utils.

PS. Przyjrzałem się również rozsądnie bc, ale w przypadku tej konkretnej pracy nie oferuje nic poza tym, co awkrobi. Jest to (jak mówi „c” w „bc”) kalkulator - kalkulator, który wymaga dużo programowania awki tego skryptu 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
źródło
4

Będę drugim wyborem R dla lesmany i zaoferuję mój pierwszy program R. Odczytuje jedną liczbę na linię na standardowym wejściu i zapisuje cztery liczby (min., Maks., Średnia, mediana) oddzielone spacjami na standardowe wyjście.

#!/usr/bin/env Rscript
a <- scan(file("stdin"), c(0), quiet=TRUE);
cat(min(a), max(a), mean(a), median(a), "\n");
Gilles
źródło
Dzięki za „drugi” (uspokajający) ... twój przykład był przydatny, ponieważ nie zdawałem sobie sprawy, że Rjest to interaktywny interfejs i Rscriptnapędza skrypty plików, które można wykonać zgodnie z twoim przykładem hash-bang lub wywoływane z poziomu skryptu bash. Skrypty mogą obsługiwać argumenty wiersza poleceń (np. stackoverflow.com/questions/2045706/... ), więc wygląda dobrze ... Również wyrażenia R mogą być używane w bashie przez -e... ale Zastanawiam się, jak Rbc
wypada w
2

Poniższy sort/ awktandem robi to:

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}'

(oblicza medianę jako średnią z dwóch wartości centralnych, jeśli liczba wartości jest parzysta)

mik
źródło
2

Biorąc wskazówki z kodu Bruce'a, tutaj jest bardziej wydajna implementacja, która nie przechowuje wszystkich danych w pamięci. Jak stwierdzono w pytaniu, zakłada się, że plik wejściowy ma (najwyżej) jedną liczbę w wierszu. Zlicza wiersze w pliku wejściowym, które zawierają liczbę kwalifikującą, i przekazuje licznik do awkpolecenia wraz z (poprzednimi) posortowanymi danymi. Na przykład, jeśli plik zawiera

6.0
4.2
8.3
9.5
1.7

wtedy wejście do awkjest w rzeczywistości

5
1.7
4.2
6.0
8.3
9.5

Następnie awkskrypt przechwytuje liczbę danych w NR==1bloku kodu i zapisuje środkową wartość (lub dwie środkowe wartości, które są uśredniane w celu uzyskania mediany), gdy je widzi.

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
  }
'
Rahul Agarwal
źródło
Witamy w systemach Unix i Linux! Dobra robota na pierwszy post. (1) Chociaż może to odpowiedzieć na pytanie, lepszym rozwiązaniem byłoby wyjaśnienie, w jaki sposób / dlaczego to robi. Standardy strony ewoluowały w ciągu ostatnich czterech lat; chociaż w 2011 r. można było zaakceptować tylko kody, preferujemy teraz wyczerpujące odpowiedzi, które dostarczą więcej wyjaśnień i kontekstu. Nie proszę o wyjaśnienie całego skryptu; tylko części, które zmieniłeś (ale jeśli chcesz wyjaśnić cały skrypt, to też jest OK). (BTW, ja rozumiem dobrze, pytam w imieniu naszych mniej doświadczonych użytkowników.) ... (ciąg dalszy)
G-Man
(Ciąg dalszy)… Nie odpowiadaj w komentarzach; edytuj swoją odpowiedź, aby była jaśniejsza i bardziej kompletna. (2) Naprawienie skryptu tak, aby nie musiał przechowywać całej tablicy w pamięci, jest dobrym ulepszeniem, ale nie jestem pewien, czy właściwe jest stwierdzenie, że twoja wersja jest „bardziej wydajna”, gdy masz trzy niepotrzebne catpolecenia; patrz UUOC . … (Ciąg dalszy)
G-Man,
(Ciąg dalszy)… (3) Twój kod jest bezpieczny, ponieważ ustawiłeś FILENAMEi wiesz, co ustawiłeś, ale ogólnie powinieneś zawsze cytować zmienne powłoki, chyba że masz dobry powód, aby tego nie robić, i jesteś na pewno wiesz co robisz. (4) Zarówno twoja odpowiedź, jak i Bruce ignorują negatywne dane wejściowe (tj. Liczby zaczynające się od -); w pytaniu nie ma nic, co sugerowałoby, że jest to prawidłowe lub pożądane zachowanie. Nie czuj się źle; minęły ponad cztery lata i najwyraźniej jestem pierwszą osobą, która to zauważyła.
G-Man,
Wprowadzono zmiany zgodnie z sugestiami. Nie wiedziałem o narzutach polecenia kota. Zawsze używał go do strumieniowania pojedynczych plików. Dzięki, że powiedziałeś mi o UUOC .....
Rahul Agarwal
Dobry. Wyeliminowałem trzeci cati dodałem do wyjaśnienia.
G-Man,
2

numJest mały awkwrapper który robi dokładnie to i więcej, np

$ 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

ratuje Cię przed odkryciem koła w ultra-przenośnym awk. Dokumenty są podane powyżej, a bezpośredni link tutaj (sprawdź także stronę GitHub ).

coderofsalvation
źródło
Łącza do ukrytego kodu internetowego, który ma zostać wykonany na komputerze użytkownika, wydaje mi się złym pomysłem. Stroną, która zawiera kod znajduje się tutaj
Gdzie był ten „battletested” Kod gospodarzem przed wprowadzeniem na github wszystkich 4 miesiące temu? Uważam za wyjątkowo podejrzane, że link do github musi zostać usunięty z polecenia curl download. O wiele łatwiej jest dowiedzieć się, jak przekazać darowiznę finansową programistom. Wygląda na to, że autor tego kodu boi się, że ludzie pójdą na github i spojrzą na (prawie nieistniejącą) historię i statystyki. Czy jest jakiś powód, by w ogóle sprawdzać tę bitwę, prócz zbierania pieniędzy?
Anthon
@BinaryZeba: zaktualizowano
coderofsalvation
@Anthon ok, usunięto część „sprawdzoną”. Nie sądzę, że jest to miejsce spisku FUD.
coderofsalvation
2

Z 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
Stéphane Chazelas
źródło
1

cat/pythonjedyne rozwiązanie - nie dowód pustego wejścia!

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
źródło
Nie pokazałeś mediany
Peter.O
@ Peter.O naprawiono.
ravwojdyla
Statystyki moduł wymaga wersji Pythona> = 3,4
Peter.O
@ Peter.O masz rację - czy to problem?
ravwojdyla
To nie jest problem, chyba że nie masz odpowiedniej wersji Pythona. To po prostu czyni go mniej przenośnym.
Peter.O,
0

Jeśli bardziej interesuje Cię użyteczność niż bycie fajnym lub sprytnym, to perljest łatwiejszy wybór niż awk. Zasadniczo będzie on na każdym * nixie ze stałym zachowaniem, i jest łatwy i darmowy do zainstalowania w systemie Windows. Myślę, że jest to również mniej tajemnicze niż awk, i będzie kilka modułów statystyk, których możesz użyć, jeśli chcesz mieć dom w połowie drogi między pisaniem go samemu a czymś takim jak R. Mój dość niesprawdzony (w rzeczywistości wiem, że ma błędy, ale działa na moje potrzeby ) napisanie perlskryptu zajęło około minuty, i sądzę, że jedyną tajemniczą częścią byłaby ta while(<>), która jest bardzo przydatnym skrótem, co oznacza, że ​​weźmiemy pliki przekazane jako argumenty wiersza poleceń, odczytujemy wiersz po kolei i wstawiamy ten wiersz w zmiennej specjalnej$_. Możesz więc umieścić to w pliku o nazwie count.pl i uruchomić jako perl count.pl myfile. Poza tym to, co się dzieje, powinno być boleśnie oczywiste.

$max = 0;
while (<>) {
 $sum = $sum + $_;
 $max = $_ if ($_ > $max);
 $count++;
}
$avg=$sum/$count;
print "$count numbers total=$sum max=$max mean=$avg\n";
Iain
źródło
3
Nie pokazałeś mediany
Peter.O
0
function median()
{
    declare -a nums=($(cat))
    printf '%s\n' "${nums[@]}" | sort -n | tail -n $((${#nums[@]} / 2 + 1)) | head -n 1
}  
David McLaughlin
źródło
Ta odpowiedź byłaby przydatna, gdyby istniało wyjaśnienie, w jaki sposób powyższy kod odpowiada na pytanie, np. Powinieneś powiedzieć, że używa Bash (nie sh) jako interpretera. Istnieje również problem z tym, jak dane są wczytywane do tablicy z pliku.
Anthony Geoghegan,