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.
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
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.
Avec GNU datamash :
$ printf '1\n2\n4\n' | datamash max 1 min 1 mean 1 median 1
4 1 2.3333333333333 2
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
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.
pythonpy fonctionne bien pour ce genre de chose:
cat file.txt | py --ji -l 'min(l), max(l), numpy.median(l), numpy.mean(l)'
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 @FUne 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
.
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]};
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.
Juste pour avoir une variété d'options présentées sur cette page, voici deux autres façons:
1: octave
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
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 ).
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)
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
}
'
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
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))"
function median()
{
declare -a nums=($(cat))
printf '%s\n' "${nums[@]}" | sort -n | tail -n $((${#nums[@]} / 2 + 1)) | head -n 1
}
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";