Existe-t-il un moyen d’obtenir les valeurs min, max, médiane et moyenne d’une liste de nombres dans une seule commande?


93

J'ai une liste de numéros dans un fichier, un par ligne. Comment puis-je obtenir les valeurs minimale, maximale, médiane et moyenne ? Je veux utiliser les résultats dans un script bash.

Bien que ma situation immédiate concerne les nombres entiers, une solution pour les nombres à virgule flottante serait utile en bout de ligne, mais une méthode de nombre entier simple convient parfaitement.


Réponses:


50

Vous pouvez utiliser le langage de programmation R .

Voici un script rapide et sale:

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

Notez le "stdin"dans scanlequel se trouve un nom de fichier spécial à lire à partir d’une entrée standard (c’est-à-dire à partir de pipes ou de redirections).

Vous pouvez maintenant rediriger vos données sur stdin vers le script R:

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

Fonctionne également pour les points flottants:

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

Si vous ne voulez pas écrire de fichier de script R, vous pouvez appeler une vraie ligne (avec un saut de ligne uniquement pour la lisibilité) dans la ligne de commande en utilisant 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

Lisez les manuels R fins sur http://cran.r-project.org/manuals.html .

Malheureusement, la référence complète est uniquement disponible en PDF. Une autre façon de lire la référence consiste à saisir ?topicnamel'invite d'une session interactive R.


Pour être complet: il existe une commande R qui affiche toutes les valeurs souhaitées et plus encore. Malheureusement, dans un format convivial, difficile à analyser par programmation.

> 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
Cela semble intéressant… je l'examinerai de plus près demain… D'après la page de Wikipédia, «R est devenu un standard de facto chez les statisticiens» ... eh bien, c'est une récompense importante ... j'ai effectivement essayé de le télécharger. l'autre jour (j'ai continué à le voir mentionné), mais je ne pouvais pas le trouver dans le dépôt Ubuntu ... je le suivrai demain ...
Peter.O.

10
dans le dépôt ubuntu (et debian?), le paquet porte le nom r-base.
Lesmana

merci, j'avais besoin de cette référence de nom :) Je ne pensais pas à r- dans le champ de recherche synaptique et il n'agissait pas sur un personnage isolé ... Je l'ai essayé maintenant, et il a l'air idéal ... Rla langue est clairement le meilleur pour mon besoin dans cette situation .. Selon la réponse de Gilles, l' Rscriptinterface pour les fichiers de script est la plus appropriée (vs R, qui est l'interface interactive) ... et R dans le terminal constitue une calculatrice pratique , ou environnement de test (comme python :)
Peter.O,

(+1) J'aime R. Je ne peux pas le recommander assez.
Dason

6
ou justecat datafile | Rscript -e 'print(summary(scan("stdin")));'
shabbychef

52

En fait, je garde un petit programme awk pour donner la somme, le nombre de données, le minimum de données, le maximum de données, la moyenne et la médiane d'une seule colonne de données numériques (y compris les nombres négatifs):

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

Le script ci-dessus lit à partir de stdin et imprime les colonnes de sortie séparées par des tabulations sur une seule ligne.


1
Aha! c'est évident (maintenant que j'ai vu votre script awk :) ... ... Il n'est pas nécessaire de continuer à vérifier les valeurs min et max lorsque le tableau est trié :) et cela signifie que vous NR==1pouvez y aller (une utilisation inutile de si) ainsi que les vérifications min / max, ainsi toutes les initialisations peuvent être situées dans la section BEGIN (bon!) ... Permettre des commentaires est une belle touche aussi .. Merci, +1 ...
Peter.O

Juste une pensée .. peut-être qu'autoriser uniquement les nombres est mieux que de refuser les commentaires (mais cela dépend de vos exigences) ..
Peter.O

1
Techniquement, awksupposons que les "nouvelles" variables sont à zéro, de sorte que dans ce cas, la BEGIN{}section est inutile. J'ai corrigé l'emballage (pas besoin d'échapper aux sauts de ligne). J'avais aussi l'habitude OFS="\t"de nettoyer la printligne et de mettre en œuvre le deuxième commentaire de @ Peter.O. (Oui, ma regex le permet ., mais comme awkinterprété 0, c'est acceptable.)
Adam Katz

1
@AdamKatz - ce sont de grands changements, mais dans l'état actuel des choses, je n'ai pas écrit le programme. Mon awkscript est maintenant sensiblement différent. J'ai presque l'impression que vous devriez prendre crédit pour le programme ci-dessus, afin de donner un crédit lorsque le crédit est dû.
Bruce Ediger

1
J'ai écrit un script Perl appelé AVG qui fait ceci et plus, d'ailleurs.
Adam Katz

48

Avec GNU datamash :

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

4
réponse la plus simple de loin pour bash, comme demandé
rfabbri

3
brew install datamashvous donne une version de travail pour macOS, si vous avez installé Hombrew.
Par Lundberg

19

Min, max et average sont assez faciles à obtenir avec 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

Le calcul de la médiane est un peu plus délicat, car vous devez trier les nombres et les stocker tous en mémoire pendant un moment ou les lire deux fois (première fois à les compter, deuxième - pour obtenir la valeur médiane). Voici un exemple qui stocke tous les nombres en mémoire:

% 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

Merci ... votre exemple est une bonne introduction à awk, pour moi .. je l'ai un peu peaufiné et mis les deux ensemble (obtenir la sensation de awk) ... j'ai utilisé awk's asortplutôt que le piped sortVoici un lien vers ma version résultante paste.ubuntu.com/612674 ... (et une note à Kim: je teste awk depuis quelques heures maintenant Travailler avec un exemple d’intérêt personnel est bien mieux pour moi) ... Une note générale aux lecteurs: Je suis toujours intéressé de voir d’autres méthodes. le plus compact le mieux. Je vais attendre un peu ...
Peter.O

17

pythonpy fonctionne bien pour ce genre de chose:

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

17

Le minimum:

jq -s min

Maximum:

jq -s max

Médian:

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

Moyenne:

jq -s add/length

Dans jql' option -s( --slurp), un tableau est créé pour les lignes d'entrée après l'analyse de chaque ligne sous forme de code JSON ou sous forme de nombre dans ce cas.


3
La solution jq mérite une mention spéciale, car elle est succincte et réutilise l’outil de manière non évidente.
jplindstrom

1
magnifique! J'aimerai pouvoir donner +2
RASG le

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.txtne semble pas tout à fait raison, peutcat
malat

6

Et une doublure (longue) en Perl, y compris la médiane:

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

Les options spéciales utilisées sont:

  • -0777 : lit le fichier entier en une fois au lieu de ligne par ligne
  • -a : autosplit dans le tableau @F

Une version de script plus lisible de la même chose serait:

#!/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 vous voulez des nombres décimaux, remplacez-les %dpar quelque chose comme %.2f.


6

Simple-r est la réponse:

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

Il utilise l'environnement R pour simplifier l'analyse statistique.


5

Juste pour avoir une variété d’options présentées sur cette page, voici encore deux façons:

1: octave

  • GNU Octave est un langage interprété de haut niveau, principalement destiné aux calculs numériques. Il fournit des fonctionnalités pour la résolution numérique de problèmes linéaires et non linéaires, ainsi que pour la réalisation d’autres expériences numériques.

Voici un exemple rapide d'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 + outils à usage unique .

Pour que bash puisse gérer les nombres à virgule flottante, ce script utilise numprocesset numaveragedepuis le package num-utils.

PS J'ai également examiné de manière raisonnable bc, mais pour ce travail particulier, il n'offre rien de plus que ce qu'il awkpropose. C'est (comme le dit le 'c' dans 'bc') une calculatrice - une calculatrice qui nécessite beaucoup de programmation comme awket ce 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

Je seconderai le choix de R de Lesmana et offrirai mon premier programme de R. Il lit un numéro par ligne sur l'entrée standard et écrit quatre nombres (min, max, moyenne, médiane) séparés par des espaces sur la sortie standard.

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

Merci pour la "seconde" (c'est rassurant) ... votre exemple était utile, car je n'avais pas réalisé directement que c'était l' Rinterface interactive, et Rscriptpilotait les fichiers scriptés, qui pouvaient être exécutés selon votre exemple hash-bang , ou invoqués depuis un script bash .. Les scripts peuvent gérer les arguments en ligne de commande (par exemple, stackoverflow.com/questions/2045706/… ) afin que tout soit beau ... Les expressions R peuvent également être utilisées dans bash via -e... mais Je me demande comment se Rcompare à bc...
Peter.O

2

Le dessous sort/ awktandem le fait:

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

(il calcule la médiane comme moyenne des deux valeurs centrales si le nombre de valeurs est pair)


2

En prenant exemple sur le code de Bruce, voici une implémentation plus efficace qui ne conserve pas l'intégralité des données en mémoire. Comme indiqué dans la question, cela suppose que le fichier d'entrée a (au plus) un nombre par ligne. Il compte les lignes du fichier d'entrée contenant un numéro de qualification et transmet le compte à la awkcommande avec (précédent) les données triées. Ainsi, par exemple, si le fichier contient

6.0
4.2
8.3
9.5
1.7

alors l'entrée awkest en fait

5
1.7
4.2
6.0
8.3
9.5

Ensuite, le awkscript capture le nombre de données dans le NR==1bloc de code et enregistre la valeur médiane (ou les deux valeurs médianes, dont la moyenne est calculée pour obtenir la médiane) lorsqu'il les voit.

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

Bienvenue sur Unix & Linux! Bon travail pour un premier post. (1) Bien que cela puisse répondre à la question, ce serait une meilleure réponse si vous pouviez expliquer comment / pourquoi cela le fait. Les normes du site ont évolué au cours des quatre dernières années; Alors que les réponses en code seul étaient acceptables en 2011, nous préférons maintenant des réponses complètes qui fournissent plus d'explications et de contexte. Je ne vous demande pas d'expliquer le script entier; juste les parties que vous avez modifiées (mais si vous voulez expliquer le script entier, c'est bien aussi). (En passant, je comprends très bien; je le demande au nom de nos utilisateurs moins expérimentés.)… (Suite)
G-Man

(Suite)… S'il vous plaît ne répondez pas dans les commentaires; éditez votre réponse pour la rendre plus claire et plus complète. (2) Corriger le script de sorte qu'il n'ait pas besoin de garder la totalité du tableau en mémoire est une bonne amélioration, mais je ne suis pas sûr qu'il soit approprié de dire que votre version est «plus efficace» lorsque vous avez trois catcommandes inutiles ; voir UUOC . … (Suite)
G-Man

(Suite)… (3) Votre code est sécurisé, car vous définissez FILENAMEet vous savez comment vous le définissez, mais, en général, vous devez toujours citer les variables de shell à moins que vous n'ayez une bonne raison de ne pas le faire et Bien sûr, vous savez ce que vous faites. (4) Votre réponse et celle de Bruce ignorent toutes les entrées négatives (c'est-à-dire les nombres commençant par -); rien dans la question n'indique qu'il s'agit d'un comportement correct ou souhaité. Ne te sens pas mal. Cela fait plus de quatre ans et, apparemment, je suis la première personne à l'avoir remarqué.
G-Man

Modifications apportées selon les suggestions. Ne savait pas à propos des frais généraux de la commande de chat. Toujours utilisé pour diffuser des fichiers uniques. Merci de m'avoir parlé de UUOC .....
Rahul Agarwal

Bien. J'ai éliminé le troisième catet ajouté à l'explication.
G-Man

2

Le numest un petit awkemballage qui fait exactement cela et plus, par exemple

$ 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

cela vous évite de réinventer la roue dans l'awk ultra-portable. Les documents sont donnés ci-dessus, et le lien direct ici (voir aussi la page GitHub ).


Les liens vers du code Web masqué à exécuter sur l'ordinateur de l'utilisateur me semblent une mauvaise idée. Le site qui contient le code réside ici

Où était ce code « battletested » hébergé avant d' être mis sur github tous 4 mois il y a? Je trouve extrêmement suspect que le lien vers github doive être retiré de la commande curl download. Il est beaucoup plus facile de savoir comment faire un don financier au développeur. On dirait que l'auteur de ce code craint que les gens aillent voir github et regardent l'histoire et les statistiques (presque inexistantes). Y a-t-il une raison pour que cette bataille soit mise à l'épreuve, mis à part le fait d'essayer de collecter des fonds?
Anthon

@BinaryZeba: mise à jour
coderofsalvation

@ Anthony ok, supprime la partie 'Battletested'. Je ne pense pas que ce soit le lieu du complot FUD.
coderofsalvation

2

Avec 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/pythonseule solution - pas de preuve d'entrée vide!

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

Vous n'avez pas montré la médiane
Peter.O

@ Peter.O fixe.
ravwojdyla

Le module de statistiques nécessite la version python> = 3.4
Peter.O,

@ Peter.O vous avez raison - est-ce un problème?
ravwojdyla

Ce n'est pas un problème à moins que vous n'ayez pas la version appropriée de python. Cela rend simplement moins portable.
Peter.O

0

Si vous êtes plus intéressé par l'utilité que par la fraîcheur ou l'intelligence, le perlchoix est plus facile que awk. En gros, ce sera sur chaque * nix avec un comportement cohérent, et est facile et gratuit à installer sur Windows. Je pense que c'est aussi moins crypté que awk, et il y aura quelques modules de statistiques que vous pourriez utiliser si vous vouliez une maison de transition entre l'écrire vous-même et quelque chose comme R. Mon assez peu testé (en fait je sais qu'il a des bugs mais qu'il fonctionne pour mes besoins ) le perlscript a pris environ une minute pour écrire, et je suppose que la seule partie cryptique serait le while(<>), qui est le raccourci très utile, ce qui signifie prendre le (s) fichier (s) passé (s) en tant qu'argument de ligne de commande, lire une ligne à la fois et mettre cette ligne dans la variable spéciale$_. Donc, vous pouvez mettre cela dans un fichier nommé count.pl et l'exécuter en tant que perl count.pl myfile. En dehors de cela, il devrait être douloureusement évident de savoir ce qui se passe.

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

3
Vous n'avez pas montré la médiane
Peter.O Le

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

Cette réponse serait utile s’il existait une explication de la façon dont le code ci-dessus répond à la question, par exemple, vous devriez dire qu’il utilise Bash (non sh) comme interprète. Il y a également un problème avec la façon dont les données sont lues dans le tableau à partir du fichier.
Anthony Geoghegan
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.