it-swarm.it

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

Ho un elenco di numeri in un file, uno per riga. Come posso ottenere i valori minimo, massimo, mediana e media? 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.

101
Peter.O

È possibile utilizzare 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")

Notare la "stdin" in scan che è 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 ?topicname nel 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 
54
lesmana

In realtà tengo un piccolo programma awk 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.

55
Bruce Ediger

Con GNU datamash :

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

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
20
gelraen

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 jq il -s (--Slurp) L'opzione crea un array per le righe di input dopo aver analizzato ciascuna riga come JSON o come numero in questo caso.

20
nisetama

pythonpy funziona bene per questo genere di cose:

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

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: legge l'intero file contemporaneamente 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, sostituisci %d con qualcosa come %.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 è 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.

6
user48270

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 .

Affinché bash gestisca numeri in virgola mobile, questo script utilizza numprocess e numaverage dal pacchetto num-utils.

PS. Ho anche dato un'occhiata ragionevole a bc, ma per questo particolare lavoro, non offre nulla oltre a quello che fa awk. È (come indica la 'c' in 'bc') una calcolatrice, una calcolatrice che richiede una programmazione molto simile a awk e 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 
5
Peter.O

In secondo luogo la scelta di lesmana di R e offro 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");

num è un piccolo wrapper awk che fa esattamente questo e altro, ad es.

$ 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 collegamento diretto qui (controlla anche pagina GitHub ).

3
coderofsalvation

Il seguente tandem sort/awk 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
mik

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 valido e passa il conteggio al comando awk 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'input per awk è effettivamente

5
1.7
4.2
6.0
8.3
9.5

Quindi lo script awk acquisisce il conteggio dei dati in NR==1 blocca il 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
  }
'
2
Rahul Agarwal

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
2

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

Se sei più interessato all'utilità piuttosto che essere bravo o intelligente, allora Perl è una scelta più semplice di awk. In linea di massima 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) Perl lo script ha impiegato circa un minuto per scrivere, e immagino che l'unica parte criptica sarebbe la while(<>), che è la scorciatoia molto utile, che significa prendere i file passati come argomenti della riga di comando, leggono una riga alla volta e inseriscono 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";
0
iain