C'è un modo per ottenere il minimo, il massimo, la mediana e la media di un elenco di numeri in un singolo comando?


93

Ho un elenco di numeri in un file, uno per riga. Come posso ottenere i valori minimo, massimo, mediano e medio ? Voglio usare i risultati in uno script bash.

Sebbene la mia situazione immediata sia per i numeri interi, una soluzione per i numeri a virgola mobile sarebbe utile lungo la linea, ma un semplice metodo intero va bene.


Risposte:


50

È possibile utilizzare il linguaggio di programmazione R .

Ecco uno script R veloce e sporco:

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

Nota "stdin"in scanquale è un nome file speciale da leggere dall'input standard (ovvero da pipe o reindirizzamenti).

Ora puoi reindirizzare i tuoi dati su stdin allo script R:

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

Funziona anche con punti mobili:

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

Se non si desidera scrivere un file di script R, è possibile richiamare un vero one-liner (con interruzione di riga solo per leggibilità) nella riga di comando utilizzando 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

Leggi i buoni manuali R su http://cran.r-project.org/manuals.html .

Purtroppo il riferimento completo è disponibile solo in PDF. Un altro modo per leggere il riferimento è digitando ?topicnameil prompt di una sessione R interattiva.


Per completezza: esiste un comando R che emette tutti i valori desiderati e altro ancora. Sfortunatamente in un formato a misura d'uomo che è difficile da analizzare a livello di codice.

> 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 

1
Sembra interessante .. Domani lo guarderò più da vicino .. Sulla base della pagina di Wikipedia, "R è diventato uno standard di fatto tra gli statistici" ... beh, questo è un riconoscimento significativo ... Ho davvero cercato di scaricarlo l'altro giorno (continuavo a vederlo menzionato), ma non riuscivo a trovarlo nel repository di Ubuntu ... Lo seguirò domani ...
Peter.O

10
nel repository ubuntu (e debian?) il pacchetto è chiamato r-base.
lesmana,

grazie, avevo bisogno di quel nome di riferimento :) Non pensavo di r- nel campo di ricerca sinaptica e non agisce su un personaggio solitario ... L'ho provato ora, e sembra ideale ... Ril linguaggio è chiaramente il migliore per le mie esigenze in questa situazione .. Secondo la risposta di Gilles, l' Rscriptinterfaccia per i file di script è la più appropriata (vs. R, che è l'interfaccia interattiva) ... e R nel terminale rende una calcolatrice pratica o ambiente di test (come python :)
Peter.O

(+1) Adoro R. Non posso raccomandarlo abbastanza.
Dason,

6
o semplicementecat datafile | Rscript -e 'print(summary(scan("stdin")));'
shabbychef,

52

In realtà tengo un piccolo programma in giro per dare la somma, il conteggio dei dati, il dato minimo, il dato massimo, la media e la mediana di una singola colonna di dati numerici (compresi i numeri negativi):

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

Lo script sopra legge da stdin e stampa colonne di output separate da tabulazione su una sola riga.


1
Aha! è ovvio (ora che ho visto il tuo script awk :) ... Non c'è bisogno di continuare a controllare min e max quando l'array è ordinato :) e questo significa che NR==1può andare (un uso inutile di if) insieme ai controlli min / max, quindi tutte le inizializzazioni possono essere localizzate nella sezione INIZIO (buono!) ... Anche consentire i commenti è un bel tocco .. Grazie, +1 ...
Peter.O

Solo un pensiero .. forse consentire solo i numeri è meglio che non consentire commenti (ma questo dipende dalle tue esigenze) ..
Peter.O

1
Tecnicamente, awksupporrà che le "nuove" variabili siano zero, quindi in questo caso la BEGIN{}sezione non è necessaria. Ho risolto il wrapping (non è nemmeno necessario sfuggire alle interruzioni di riga). Ho anche usato OFS="\t"per ripulire la printlinea e implementato il secondo commento di @ Peter.O. (Sì, il mio regex lo permette ., ma come awkinterpretato come 0, questo è accettabile.)
Adam Katz

1
@AdamKatz - questi sono grandi cambiamenti, ma così com'è, non ho scritto il programma. La mia awksceneggiatura ora è sostanzialmente diversa. Sento quasi che dovresti prenderti il ​​merito per il programma di cui sopra, al fine di dare credito quando il credito è dovuto.
Bruce Ediger,

1
A proposito, ho scritto uno script perl chiamato avg che fa questo e altro.
Adam Katz,

47

Con datamash GNU :

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

4
la risposta più semplice di gran lunga per bash, come chiesto
rfabbri,

3
brew install datamashti dà una versione funzionante per macOS, se hai installato Hombrew.
Per Lundberg,

19

Min, max e media sono abbastanza facili da ottenere con 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

Il calcolo della mediana è un po 'più complicato, poiché è necessario ordinare i numeri e memorizzarli tutti in memoria per un po' o leggerli due volte (prima volta per contarli, secondo - per ottenere un valore mediano). Ecco un esempio che memorizza tutti i numeri in memoria:

% 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

Grazie ... il tuo esempio è un buon lead-in per Awk, per me .. L'ho modificato un po 'e ho messo insieme i due (dando la sensazione di Awk) ... Ho usato Awk asortinvece che il piping sorte sembra ordinare correttamente numeri interi e decimali. Ecco un link alla mia versione risultante paste.ubuntu.com/612674 ... (E una nota a Kim: sto sperimentando awk da un paio d'ore ormai Lavorare con un esempio di interesse personale è molto meglio per me) ... Una nota generale per i lettori: sono ancora interessato a vedere altri metodi. più compatto è, meglio è. Aspetterò un po '...
Peter

17

pythonpy funziona bene per questo genere di cose:

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

17

Minimo:

jq -s min

Massimo:

jq -s max

Mediano:

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

Media:

jq -s add/length

In jqla -s( --slurpopzione) crea un array per le linee di ingresso dopo l'analisi ogni riga come JSON, o come un numero in questo caso.


3
La soluzione jq è degna di una menzione speciale, poiché è concisa e riutilizza lo strumento in modo non ovvio.
jplindstrom,

1
bellissimo! vorrei poter dare +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]};

echo file.txtnon sembra del tutto giusto, forsecat
malat,

6

E un liner (lungo) Perl, inclusa la 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;'

Le opzioni speciali utilizzate sono:

  • -0777 : leggi l'intero file in una sola volta anziché riga per riga
  • -a : autosplit nell'array @F

Una versione dello script più leggibile della stessa cosa sarebbe:

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

Se vuoi decimali, sostituiscili %dcon qualcosa di simile %.2f.


6

Simple-r è la risposta:

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

Utilizza l'ambiente R per semplificare l'analisi statistica.


5

Solo per avere una varietà di opzioni presentate in questa pagina, ecco altri due modi:

1: ottava

  • GNU Octave è un linguaggio interpretato di alto livello, destinato principalmente ai calcoli numerici. Fornisce funzionalità per la soluzione numerica di problemi lineari e non lineari e per l'esecuzione di altri esperimenti numerici.

Ecco un esempio di ottava veloce.

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 + strumenti monouso .

Per bash per gestire i numeri in virgola mobile, questo script utilizza numprocesse numaveragedal pacchettonum-utils .

PS. Ho anche dato un'occhiata ragionevole bc, ma per questo particolare lavoro, non offre nulla al di là di ciò che awkfa. È (come afferma la 'c' in 'bc') una calcolatrice - una calcolatrice che richiede molta programmazione come awke questo script 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 

4

Io la seconda scelta di Lesmana di R e di offrire il mio primo programma R. Legge un numero per riga sull'input standard e scrive quattro numeri (min, max, media, mediana) separati da spazi sull'output standard.

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

Grazie per il "secondo" (è rassicurante) ... il tuo esempio è stato utile, dato che non ho capito subito che Rè l'interfaccia interattiva e Rscriptguida i file con script, che possono essere eseguibili come nel tuo esempio hash-bang o invocato dall'interno di uno script bash. Gli script sono in grado di gestire gli argomenti della riga di comando (ad es. stackoverflow.com/questions/2045706/… ), quindi sembrano buoni ... Anche le espressioni R possono essere utilizzate in bash tramite il -e... ma Mi chiedo come sia Rparagonabile a bc...
Peter

2

Il seguito sort/ awktandem lo fa:

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

(calcola la mediana come media dei due valori centrali se il conteggio dei valori è pari)


2

Prendendo spunto dal codice di Bruce, ecco un'implementazione più efficiente che non mantiene tutti i dati in memoria. Come indicato nella domanda, si presume che il file di input abbia (al massimo) un numero per riga. Conta le righe nel file di input che contengono un numero qualificante e passa il conteggio al awkcomando insieme a (precedenti) i dati ordinati. Quindi, ad esempio, se il file contiene

6.0
4.2
8.3
9.5
1.7

quindi l'ingresso a awkè in realtà

5
1.7
4.2
6.0
8.3
9.5

Quindi lo awkscript acquisisce il conteggio dei dati nel NR==1blocco di codice e salva il valore medio (o i due valori medi, che sono mediati per ottenere la mediana) quando li vede.

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

Benvenuti in Unix e Linux! Ottimo lavoro per un primo post. (1) Anche se questo può rispondere alla domanda, sarebbe una risposta migliore se tu potessi spiegare come / perché lo fa. Gli standard del sito si sono evoluti negli ultimi quattro anni; mentre le risposte di solo codice erano accettabili nel 2011, ora preferiamo risposte complete che forniscano ulteriori spiegazioni e contesto. Non ti sto chiedendo di spiegare l'intera sceneggiatura; solo le parti che hai cambiato (ma se vuoi spiegare l'intero script, va bene lo stesso). (A proposito, lo capisco bene; lo sto chiedendo a nome dei nostri utenti meno esperti.) ... (proseguendo)
G-Man,

(Proseguendo) ... Per favore non rispondere nei commenti; modifica la tua risposta per renderla più chiara e completa. (2) Correggere lo script in modo che non sia necessario conservare l'intero array in memoria è un buon miglioramento, ma non sono sicuro che sia appropriato dire che la tua versione è "più efficiente" quando hai tre catcomandi non necessari ; vedi UUOC . ... (proseguendo)
G-Man,

(Continua) ... (3) Il tuo codice è sicuro, dal momento che hai impostato FILENAMEe sai a cosa lo hai impostato, ma, in generale, dovresti sempre citare le variabili della shell a meno che tu non abbia una buona ragione per non farlo, e tu sei sicuro di sapere cosa stai facendo. (4) Sia la tua risposta che Bruce ignorano l'input negativo (ovvero i numeri che iniziano con -); non c'è nulla nella domanda che suggerisca che si tratti di un comportamento corretto o desiderato. Non stare male; sono passati più di quattro anni e, a quanto pare, sono la prima persona che l'ha notato.
G-Man,

Modifiche apportate come da suggerimenti. Non sapevo del sovraccarico del comando del gatto. Lo ha sempre usato per lo streaming di singoli file. Grazie per avermi parlato di UUOC .....
Rahul Agarwal,

Buono. Ho eliminato il terzo cate aggiunto alla spiegazione.
G-Man,

2

Il numè un piccolo awkinvolucro che fa esattamente questo e molto altro, ad esempio,

$ 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

ti salva dal reinventare la ruota nel awk ultra-portatile. I documenti sono riportati sopra e il link diretto qui (controlla anche la pagina GitHub ).


I collegamenti a codice Web oscurato da eseguire nel computer dell'utente mi sembrano una cattiva idea. Il sito che contiene il codice risiede qui

Cui è stato questo codice "battletested" ha ospitato, prima di essere messo su GitHub tutti 4 mesi fa? Trovo estremamente sospetto che il collegamento a github debba essere rimosso dal comando di download dell'arricciatura. È molto più facile scoprire come donare finanziariamente allo sviluppatore. Sembra che l'autore di quel codice abbia paura che le persone possano andare su Github e guardare la storia e le statistiche (quasi inesistenti). C'è qualche motivo per chiamare questa battaglia messa alla prova, a parte il tentativo di raccogliere fondi?
Anthon,

@BinaryZeba: aggiornato
coderofsalvation il

@Anthon ok, rimosso la parte 'battletested'. Non penso che questo sia il posto giusto per cospirare FUD.
coderofsalvation

2

Con 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/pythonunica soluzione - non a prova di input vuoto!

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

Non hai mostrato la mediana
Peter.O

@ Peter.O riparato.
Ravwojdyla,

Il modulo statistico richiede la versione di python> = 3.4
Peter.O

@ Peter.O hai ragione - è un problema?
Ravwojdyla,

Non è un problema a meno che tu non abbia la versione di Python appropriata. Lo rende solo meno portatile.
Peter

0

Se sei più interessato all'utilità piuttosto che essere bravo o intelligente, allora perlè una scelta più semplice di awk. Nel complesso sarà su ogni * nix con un comportamento coerente ed è facile e gratuito da installare su Windows. Penso che sia anche meno enigmatico di awk, e ci saranno alcuni moduli statistici che potresti usare se volessi una casa a metà strada tra scriverlo tu stesso e qualcosa come R. Il mio abbastanza non testato (in realtà so che ha dei bug ma funziona per i miei scopi ) la perlsceneggiatura ha impiegato circa un minuto per scrivere, e immagino che l'unica parte criptica sarebbe lawhile(<>) , che è la scorciatoia molto utile, il che significa che prendere i file passati come argomenti della riga di comando, leggere una riga alla volta e mettere quella riga nella variabile speciale$_. Quindi potresti metterlo in un file chiamato count.pl ed eseguirlo come perl count.pl myfile. A parte ciò, dovrebbe essere dolorosamente ovvio cosa sta succedendo.

$max = 0;
while (<>) {
 $sum = $sum + $_;
 $max = $_ if ($_ > $max);
 $count++;
}
$avg=$sum/$count;
print "$count numbers total=$sum max=$max mean=$avg\n";

3
Non hai mostrato la mediana
Peter

0
function median()
{
    declare -a nums=($(cat))
    printf '%s\n' "${nums[@]}" | sort -n | tail -n $((${#nums[@]} / 2 + 1)) | head -n 1
}  

Questa risposta sarebbe utile se ci fosse una spiegazione di come il codice sopra risponde alla domanda, ad esempio, dovresti dire che sta usando Bash (non sh) come interprete. C'è anche un problema con il modo in cui i dati vengono letti nell'array dal file.
Anthony Geoghegan,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.