web-dev-qa-db-fra.com

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

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 soit pour les entiers, une solution pour les nombres à virgule flottante serait utile sur toute la ligne, mais une méthode simple pour les entiers est très bien.

101
Peter.O

Vous pouvez utiliser le langage de programmation R .

Voici un script R rapide et sale:

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

Noter la "stdin" in scan qui est un nom de fichier spécial à lire à partir d'une entrée standard (c'est-à-dire à partir de canaux ou de redirections).

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

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

Fonctionne également pour les virgules flottantes:

$ 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 un fichier de script R, vous pouvez invoquer un vrai one-liner (avec 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 bons manuels R à http://cran.r-project.org/manuals.html .

Malheureusement, la référence complète n'est disponible qu'en PDF. Une autre façon de lire la référence est de taper ?topicname dans l'invite d'une session R interactive.


Pour être complet: il existe une commande R qui génère toutes les valeurs souhaitées et plus encore. Malheureusement, dans un format convivial pour l'homme, difficile à analyser par programme.

> 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

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.

55
Bruce Ediger

Avec 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 et moyenne 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 certain temps ou les lire deux fois (première fois pour 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
20
gelraen

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 jq, l'option -s (--Slurp) Crée un tableau pour les lignes d'entrée après avoir analysé chaque ligne en JSON, ou en tant que nombre dans ce cas.

20
nisetama

pythonpy fonctionne bien pour ce genre de chose:

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

Et une doublure Perl one- (longue), 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: lire tout le fichier en une seule fois au lieu de ligne par ligne
  • -a: fractionnement automatique 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 décimales, remplacez %d avec quelque chose comme %.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 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.

6
user48270

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

1: octave

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

Voici un exemple d'octave rapide.

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 gère les nombres à virgule flottante, ce script utilise numprocess et numaverage du package num-utils.

PS. J'ai également jeté un coup d'œil raisonnable à bc, mais pour ce travail particulier, il n'offre rien au-delà de ce que awk fait. C'est (comme le 'c' dans les états 'bc') une calculatrice - une calculatrice qui nécessite beaucoup de programmation comme awk et 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 
5
Peter.O

Je vais second le choix de lesmana de R et offrir mon premier programme R. Il lit un nombre 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");

num est un minuscule wrapper awk qui fait exactement cela et plus encore, 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

il vous évite de réinventer la roue dans le awk ultra-portable. Les documents sont donnés ci-dessus, et le lien direct ici (vérifiez également page GitHub ).

3
coderofsalvation

Le tandem sort/awk ci-dessous 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 la moyenne des deux valeurs centrales si le nombre de valeurs est pair)

2
mik

En s'inspirant du code de Bruce, voici une implémentation plus efficace qui ne conserve pas toutes les données en mémoire. Comme indiqué dans la question, il suppose que le fichier d'entrée a (au plus) un numéro par ligne. Il compte les lignes du fichier d'entrée qui contiennent un nombre éligible et transmet le nombre à la commande awk avec (précédant) 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 de awk est en fait

5
1.7
4.2
6.0
8.3
9.5

Ensuite, le script awk capture le nombre de données dans le NR==1 code bloc et enregistre la valeur moyenne (ou les deux valeurs moyennes, qui sont moyennées pour donner la médiane) quand 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
  }
'
2
Rahul Agarwal

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
2
Stéphane Chazelas

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

Si vous êtes plus intéressé par l'utilité plutôt que d'être cool ou intelligent, alors Perl est un choix plus facile que awk. Dans l'ensemble, il sera installé sur chaque * nix avec un comportement cohérent, et il est facile et gratuit à installer sur Windows. Je pense que c'est aussi moins cryptique que awk, et il y aura des modules de statistiques que vous pourriez utiliser si vous vouliez un compromis entre l'écrire vous-même et quelque chose comme R. Mon assez non testé (en fait, je sais qu'il a des bugs mais cela fonctionne pour mes besoins) Perl script a pris environ une minute pour écrire, et je suppose que la seule partie cryptique serait la while(<>), qui est le raccourci très utile, ce qui signifie prendre le ou les fichiers passés comme arguments de ligne de commande, lisez une ligne à la fois et placez cette ligne dans la variable spéciale $_. Vous pouvez donc mettre cela dans un fichier appelé count.pl et l'exécuter en tant que Perl count.pl myfile. En dehors de cela, il devrait être douloureusement évident de 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";
0
iain