¿Hay alguna manera de obtener el mínimo, el máximo, la mediana y el promedio de una lista de números en un solo comando?

93

Tengo una lista de números en un archivo, uno por línea. ¿Cómo puedo obtener los valores mínimo, máximo, mediano y promedio ? Quiero usar los resultados en un script bash.

Aunque mi situación inmediata es para enteros, una solución para números de punto flotante sería útil en el futuro, pero un método entero simple está bien.

Peter.O
fuente
stackoverflow.com/questions/3122442/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Respuestas:

50

Puede utilizar el lenguaje de programación R .

Aquí hay un script R rápido y sucio:

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

Tenga "stdin"en cuenta scanque hay un nombre de archivo especial para leer desde la entrada estándar (es decir, desde tuberías o redirecciones).

Ahora puede redirigir sus datos sobre stdin al script R:

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

También funciona para puntos flotantes:

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

Si no desea escribir un archivo de secuencia de comandos R, puede invocar una línea única verdadera (con salto de línea solo para facilitar la lectura) en la línea de comando usando 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

Lea los excelentes manuales de R en http://cran.r-project.org/manuals.html .

Lamentablemente, la referencia completa solo está disponible en PDF. Otra forma de leer la referencia es escribiendo ?topicnameel aviso de una sesión interactiva de R.


Para completar: hay un comando R que genera todos los valores que desea y más. Desafortunadamente, en un formato amigable para los humanos que es difícil de analizar mediante programación.

> 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
fuente
1
Parece interesante ... Lo examinaré más de cerca mañana ... Según la página de Wikipedia, "R se ha convertido en un estándar de facto entre los estadísticos" ... bueno, eso es un gran elogio ... Intenté descargarlo. el otro día (seguí viéndolo mencionado), pero no pude encontrarlo en el repositorio de Ubuntu ... Lo seguiré mañana ...
Peter.O
10
en el repositorio ubuntu (y debian?) el paquete se llama r-base.
lesmana
gracias, necesitaba esa referencia de nombre :) No pensé en r- en el campo de búsqueda sináptica y no actúa sobre un personaje solitario ... Lo he probado ahora, y se ve ideal ... Rel lenguaje es claramente el mejor para mi requerimiento en esta situación. Según la respuesta de Gilles, la Rscriptinterfaz para los archivos de script es la más apropiada (vs. R, que es la interfaz interactiva) ... y R en la terminal es una calculadora útil , o entorno de prueba (como python :)
Peter.O
(+1) Amo a R. No puedo recomendarlo lo suficiente.
Dason
66
o simplementecat datafile | Rscript -e 'print(summary(scan("stdin")));'
shabbychef
52

De hecho, mantengo un pequeño programa awk para dar la suma, el recuento de datos, el dato mínimo, el dato máximo, la media y la mediana de una sola columna de datos numéricos (incluidos los números negativos):

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

El script anterior lee de stdin e imprime columnas de salida separadas por tabuladores en una sola línea.

Bruce Ediger
fuente
1
¡Ajá! es obvio (ahora que he visto tu script awk :) ... No hay necesidad de seguir buscando min y max cuando se ordena la matriz :) y eso significa que NR==1puede irse (un uso inútil de- si) junto con las comprobaciones mín. / máx., por lo que todas las inicializaciones se pueden ubicar en la sección COMENZAR (¡bueno!) ... Permitir comentarios también es un buen toque ... Gracias, +1 ...
Peter.O
Solo un pensamiento ... tal vez permitir solo números es mejor que no permitir comentarios (pero eso depende de sus requisitos) ...
Peter
1
Técnicamente, awkasumirá que las variables "nuevas" son cero, por lo que en este caso la BEGIN{}sección es innecesaria. He arreglado el ajuste (tampoco es necesario escapar de los saltos de línea). También solía OFS="\t"limpiar la printlínea e implementé el segundo comentario de @ Peter.O. (Sí, mi expresión regular lo permite ., pero como lo awkinterpreta 0, eso es aceptable.)
Adam Katz
1
@AdamKatz: estos son grandes cambios, pero tal como están las cosas, no escribí el programa. Mi awkguión ahora es sustancialmente diferente. Casi siento que debería tomar crédito por el programa anterior, a fin de dar crédito donde se debe.
Bruce Ediger
1
Escribí un script en perl llamado avg que hace esto y más, por cierto.
Adam Katz
48

Con GNU datamash :

$ printf '1\n2\n4\n' | datamash max 1 min 1 mean 1 median 1
4   1   2.3333333333333 2
Cuonglm
fuente
44
La respuesta más simple con diferencia para bash, como se le preguntó
rfabbri
3
brew install datamashle ofrece una versión funcional para macOS, si tiene instalado Hombrew.
Por Lundberg
19

Mínimo, máximo y promedio son bastante fáciles de obtener 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

Calcular la mediana es un poco más complicado, ya que necesita ordenar los números y almacenarlos en la memoria por un tiempo o leerlos dos veces (la primera vez para contarlos, la segunda, para obtener el valor de la mediana). Aquí hay un ejemplo que almacena todos los números en la 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
gelraen
fuente
Gracias ... su ejemplo es una buena introducción a awk, para mí ... Lo modifiqué un poco y puse los dos juntos (obteniendo la sensación de awk) ... He usado awk en asortlugar de la tubería. sort, y parece ordenar correctamente los enteros y decimales. Aquí hay un enlace a mi versión resultante paste.ubuntu.com/612674 ... (Y una nota para Kim: He estado experimentando con awk durante un par de horas) Trabajar con un ejemplo de interés personal es mucho mejor para mí) ... Una nota general para los lectores: todavía estoy interesado en ver otros métodos. cuanto más compacto, mejor. Esperaré un momento ...
Peter
17

Pythonpy funciona bien para este tipo de cosas:

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

Mínimo:

jq -s min

Máximo:

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

Promedio:

jq -s add/length

En jqla opción -s( --slurp) crea una matriz para las líneas de entrada después de analizar cada línea como JSON, o como un número en este caso.

nisetama
fuente
3
La solución jq merece una mención especial, ya que es sucinta y reutiliza la herramienta de una manera no obvia.
jplindstrom
1
¡hermosa! Ojalá pudiera dar +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]};
No un número
fuente
echo file.txttal vez no parece del todo correctocat
malat
6

Y un revestimiento Perl one- (largo), que incluye 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;'

Las opciones especiales utilizadas son:

  • -0777 : lea todo el archivo a la vez en lugar de línea por línea
  • -a : autosplit en la matriz @F

Una versión de script más legible de lo mismo sería:

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

Si desea decimales, reemplácelos %dcon algo como %.2f.

mivk
fuente
6

Simple-r es la respuesta:

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

Utiliza el entorno R para simplificar el análisis estadístico.

usuario48270
fuente
5

Solo por el hecho de tener una variedad de opciones presentadas en esta página, aquí hay dos formas más:

1: octava

  • GNU Octave es un lenguaje interpretado de alto nivel, destinado principalmente a cálculos numéricos. Proporciona capacidades para la solución numérica de problemas lineales y no lineales, y para realizar otros experimentos numéricos.

Aquí hay un ejemplo rápido de octava.

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 + herramientas de un solo propósito .

Para que bash maneje números de punto flotante, este script usa numprocessy numaveragedesde el paquete num-utils.

PD. También he tenido una visión razonable bc, pero para este trabajo en particular, no ofrece nada más allá de lo que awkofrece. Es (como dice la 'c' en 'bc') una calculadora, una calculadora que requiere mucha programación awky este 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 
Peter.O
fuente
4

Voy a la segunda opción de Lesmana de R y ofrecer mi primer programa de I. Lee un número por línea en la entrada estándar y escribe cuatro números (mínimo, máximo, promedio, mediano) separados por espacios en la salida estándar.

#!/usr/bin/env Rscript
a <- scan(file("stdin"), c(0), quiet=TRUE);
cat(min(a), max(a), mean(a), median(a), "\n");
Gilles
fuente
Gracias por el "segundo" (es tranquilizador) ... su ejemplo fue útil, ya que no me di cuenta de inmediato de que Res la interfaz interactiva, y Rscriptmaneja los archivos con secuencias de comandos, que pueden ser ejecutables según su ejemplo hash-bang , o invocado desde un script bash. Los scripts pueden manejar argumentos de línea de comandos (por ejemplo, stackoverflow.com/questions/2045706/… ) para que se vea bien ... También se pueden usar expresiones R en bash a través de -e... pero me pregunto cómo Rse compara con bc...
Peter.O
2

El siguiente sort/ awktándem lo hace:

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

(calcula la mediana como media de los dos valores centrales si el recuento de valores es par)

mik
fuente
2

Tomando señales del código de Bruce, aquí hay una implementación más eficiente que no mantiene todos los datos en la memoria. Como se indicó en la pregunta, se supone que el archivo de entrada tiene (como máximo) un número por línea. Cuenta las líneas en el archivo de entrada que contienen un número calificado y pasa la cuenta al awkcomando junto con (antes) los datos ordenados. Entonces, por ejemplo, si el archivo contiene

6.0
4.2
8.3
9.5
1.7

entonces la entrada a awkes en realidad

5
1.7
4.2
6.0
8.3
9.5

Luego, el awkscript captura el recuento de datos en el NR==1bloque de código y guarda el valor medio (o los dos valores medios, que se promedian para obtener la mediana) cuando los ve.

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
fuente
¡Bienvenido a Unix y Linux! Buen trabajo para un primer post. (1) Si bien esto puede responder la pregunta, sería una mejor respuesta si pudiera explicar cómo / por qué lo hace. Los estándares del sitio han evolucionado en los últimos cuatro años; Si bien las respuestas de solo código fueron aceptables en 2011, ahora preferimos respuestas completas que brinden más explicaciones y contexto. No te estoy pidiendo que expliques el guión completo; solo las partes que cambiaste (pero si quieres explicar todo el script, también está bien). (Por cierto, lo entiendo bien; pregunto en nombre de nuestros usuarios menos experimentados.) ... (Cont.)
G-Man
(Cont.) ... Por favor no responda en comentarios; edite su respuesta para que sea más clara y completa. (2) Arreglar el script de modo que no necesite mantener toda la matriz en la memoria es una buena mejora, pero no estoy seguro de si es apropiado decir que su versión es "más eficiente" cuando tiene tres catcomandos innecesarios ; ver UUOC . ... (Continúa)
G-Man
(Cont.) ... (3) Su código es seguro, ya que lo configura FILENAMEy sabe a qué lo configura, pero, en general, siempre debe citar las variables de shell a menos que tenga una buena razón para no hacerlo, y Seguro que sabes lo que estás haciendo. (4) Tanto su respuesta como la de Bruce ignoran la entrada negativa (es decir, los números que comienzan con -); No hay nada en la pregunta que sugiera que este sea el comportamiento correcto o deseado. No te sientas mal; Han pasado más de cuatro años y, aparentemente, soy la primera persona que se da cuenta.
G-Man
Ediciones realizadas según sugerencias. No sabía sobre los gastos generales del comando gato. Siempre lo usé para transmitir archivos individuales. Gracias por contarme sobre UUOC .....
Rahul Agarwal
Bueno. Eliminé el tercero caty agregué la explicación.
G-Man
2

El numes un pequeño awkcontenedor que hace exactamente esto y más, por ejemplo

$ 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

le evita reinventar la rueda en el awk ultraportátil. Los documentos se proporcionan arriba y el enlace directo aquí (consulte también la página de GitHub ).

coderofsalvación
fuente
Los enlaces a código web oculto para ejecutar en la computadora del usuario me parecen una mala idea. El sitio que contiene el código reside aquí
¿Dónde estaba este código "combativo" alojado antes de ser puesto en github todos los 4 meses atrás? Me parece extremadamente sospechoso que el enlace a github tenga que despegarse del comando curl download. Es mucho más fácil descubrir cómo donar financieramente al desarrollador. Parece que el autor de ese código teme que la gente vaya a github y mire la historia y las estadísticas (casi inexistentes). ¿Hay alguna razón para llamar a esta batalla probada, aparte de tratar de recaudar dinero?
Anthon
@BinaryZeba: actualizado
coderofsalvation
@Anthon está bien, eliminé la parte 'probada en batalla'. No creo que este sea el lugar para la conspiración de 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
Stéphane Chazelas
fuente
1

cat/pythonúnica solución: ¡ no es una prueba de entrada vacía!

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
fuente
No has mostrado la mediana
Peter
@ Peter.O arreglado.
ravwojdyla
El módulo de estadísticas requiere la versión de python> = 3.4
Peter.O
@ Peter.O tienes razón: ¿es un problema?
ravwojdyla
No es un problema a menos que no tenga la versión adecuada de Python. Simplemente lo hace menos portátil.
Peter.O
0

Si está más interesado en la utilidad en lugar de ser genial o inteligente, entonces perles una opción más fácil que awk. En general, estará en cada * nix con un comportamiento constante, y es fácil y gratuito de instalar en Windows. Creo que también es menos críptico que awkeso, y habrá algunos módulos de estadísticas que podrías usar si quisieras un punto intermedio entre escribirlo tú mismo y algo como R. Mi bastante no probado (de hecho, sé que tiene errores pero funciona para mis propósitos ) el perlguión tardó aproximadamente un minuto en escribirse, y supongo que la única parte críptica sería while(<>), que es la taquigrafía muy útil, lo que significa tomar los archivos pasados ​​como argumentos de línea de comandos, leer una línea a la vez y poner esa línea en la variable especial$_. Entonces podría poner esto en un archivo llamado count.pl y ejecutarlo como perl count.pl myfile. Aparte de eso, debería ser dolorosamente obvio lo que está sucediendo.

$max = 0;
while (<>) {
 $sum = $sum + $_;
 $max = $_ if ($_ > $max);
 $count++;
}
$avg=$sum/$count;
print "$count numbers total=$sum max=$max mean=$avg\n";
iain
fuente
3
No has mostrado la mediana
Peter
0
function median()
{
    declare -a nums=($(cat))
    printf '%s\n' "${nums[@]}" | sort -n | tail -n $((${#nums[@]} / 2 + 1)) | head -n 1
}  
David McLaughlin
fuente
Esta respuesta sería útil si hubiera una explicación de cómo el código anterior responde a la pregunta, por ejemplo, debería decir que está usando Bash (no sh) como intérprete. También hay un problema con la forma en que los datos se leen en la matriz desde el archivo.
Anthony Geoghegan