Gibt es eine Möglichkeit, den Min, Max, Median und Durchschnitt einer Liste von Zahlen in einem einzigen Befehl zu ermitteln?


93

Ich habe eine Liste von Nummern in einer Datei, eine pro Zeile. Wie kann ich den Minimal-, Maximal-, Median- und Durchschnittswert ermitteln ? Ich möchte die Ergebnisse in einem Bash-Skript verwenden.

Obwohl meine unmittelbare Situation Ganzzahlen betrifft, wäre eine Lösung für Gleitkommazahlen auf der ganzen Linie nützlich, aber eine einfache Ganzzahlmethode ist in Ordnung.


Antworten:


50

Sie können die Programmiersprache R verwenden .

Hier ist ein schnelles und schmutziges R-Skript:

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

Beachten Sie die "stdin"inscan die ein spezielles Dateiname ist von der Standardeingabe zu lesen (das heißt , aus Rohren oder Umleitungen).

Jetzt können Sie Ihre Daten über stdin an das R-Skript umleiten:

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

Funktioniert auch für Gleitkommazahlen:

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

Wenn Sie keine R-Skriptdatei schreiben möchten, können Sie in der Befehlszeile einen echten Einzeiler (mit Zeilenumbruch nur zur besseren Lesbarkeit) aufrufen, indem Sie Folgendes verwenden 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

Lesen Sie die feinen R-Handbücher unter http://cran.r-project.org/manuals.html .

Leider ist die vollständige Referenz nur als PDF verfügbar. Eine andere Möglichkeit, die Referenz zu lesen, besteht ?topicnamedarin, die Eingabeaufforderung einer interaktiven R-Sitzung einzugeben.


Der Vollständigkeit halber gibt es einen R-Befehl, der alle gewünschten Werte und mehr ausgibt. Leider in einem menschenfreundlichen Format, das sich programmgesteuert nur schwer analysieren lässt.

> 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
Es sieht interessant aus. Ich werde es mir morgen genauer ansehen. Basierend auf der Wikipedia-Seite ist "R ein De-facto-Standard unter Statistikern geworden". Nun, das ist eine bedeutende Auszeichnung. Ich habe wirklich versucht, es herunterzuladen
neulich

10
im ubuntu (und debian?) repo heißt das paket r-base.
Lesmana

Danke, ich brauchte diese Namensreferenz :) Ich habe nicht an r- im synaptischen Suchfeld gedacht und es wirkt sich nicht auf einen einzelnen Charakter aus ... Ich habe es jetzt ausprobiert und es sieht ideal aus RSprache ist in dieser Situation eindeutig das Beste für meine Anforderung. Laut Gilles 'Antwort ist die RscriptSchnittstelle zu Skriptdateien am besten geeignet (im Gegensatz Rzur interaktiven Schnittstelle) ... und R im Terminal ist ein praktischer Taschenrechner , oder Testumgebung (wie Python :)
Peter.O

(+1) Ich liebe R. Ich kann es nicht genug empfehlen.
Dason

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

52

Ich verwende ein kleines awk-Programm, um die Summe, die Datenanzahl, das minimale Datum, das maximale Datum, den Mittelwert und den Median einer einzelnen Spalte numerischer Daten (einschließlich negativer Zahlen) anzugeben:

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

Das obige Skript liest von stdin und druckt tabulatorgetrennte Ausgabespalten in einer einzelnen Zeile.


1
Aha! es ist offensichtlich (jetzt, wo ich dein awk-Skript gesehen habe :) ... es besteht keine Notwendigkeit mehr, nach min und max zu suchen, wenn das Array sortiert ist :) und das bedeutet, dass das NR==1gehen kann (eine nutzlose Verwendung von if) zusammen mit den min / max Checks, damit alle Initialisierungen in der BEGIN Sektion liegen (gut!) ... Kommentare
zuzulassen

Nur ein Gedanke ... vielleicht ist es besser, nur
numerische Zeichen

1
Technisch awkwird davon ausgegangen, dass "neue" Variablen Null sind, sodass in diesem Fall der BEGIN{}Abschnitt nicht erforderlich ist. Ich habe den Zeilenumbruch behoben (es ist auch nicht nötig, die Zeilenumbrüche zu umgehen). Ich habe auch OFS="\t"die printZeile aufgeräumt und den zweiten Kommentar von @ Peter.O implementiert. (Ja, meine Regex erlaubt ., aber als awkinterpretiert das als 0, das ist akzeptabel.)
Adam Katz

1
@AdamKatz - das sind großartige Änderungen, aber so wie es aussieht, habe ich das Programm nicht geschrieben. Mein awkSkript ist jetzt wesentlich anders. Ich habe fast das Gefühl, Sie sollten das obige Programm anerkennen, um Kredit zu geben, wo Kredit fällig ist.
Bruce Ediger

1
Ich habe ein Perl-Skript namens avg geschrieben , das dies und mehr tut.
Adam Katz

47

Mit GNU Datamash :

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

4
mit Abstand einfachste Antwort für bash, wie gefragt
rfabbri

3
brew install datamashgibt dir eine funktionierende Version für macOS, wenn du Hombrew installiert hast.
Per Lundberg

19

Min, Max und Mittel sind mit awk ziemlich einfach zu bekommen:

% 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

Das Berechnen des Medians ist etwas kniffliger, da Sie Zahlen sortieren und eine Weile speichern müssen oder sie zweimal lesen müssen (erstens, um sie zu zählen, zweitens, um den Medianwert zu erhalten). Hier ist ein Beispiel, in dem alle Zahlen gespeichert sind:

% 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

Danke ... Ihr Beispiel ist für mich ein guter Einstieg in awk. Ich habe es ein wenig optimiert und die beiden zusammengefügt (um das Gefühl von awk zu bekommen). Ich habe awks asortanstatt der Piped verwendet sortHier ist ein Link zu meiner resultierenden Version paste.ubuntu.com/612674 ... (Und ein Hinweis an Kim: Ich habe jetzt seit ein paar Stunden mit awk experimentiert Die Arbeit mit einem Beispiel für persönliches Interesse ist für mich viel besser. je kompakter desto besser. Ich werde eine Weile warten ...
Peter.O

17

Pythonpy funktioniert gut für diese Art von Dingen:

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

17

Minimum:

jq -s min

Maximal:

jq -s max

Median:

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

Durchschnittlich:

jq -s add/length

Mit jqder Option -s( --slurp) wird ein Array für die Eingabezeilen erstellt, nachdem jede Zeile als JSON oder in diesem Fall als Zahl analysiert wurde.


3
Die jq-Lösung verdient eine besondere Erwähnung, da sie prägnant ist und das Tool auf nicht offensichtliche Weise neu verwendet.
Jplindstrom

1
schön! Ich wünschte, ich könnte +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]};

echo file.txtsieht vielleicht nicht ganz richtig auscat
malat

6

Und ein Perl-Einzeiler (lang), einschließlich Median:

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

Die speziellen Optionen sind:

  • -0777 : Lesen Sie die gesamte Datei auf einmal statt Zeile für Zeile
  • -a : Autosplit in das @F-Array

Eine besser lesbare Skriptversion desselben wäre:

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

Wenn Sie Dezimalstellen wünschen, ersetzen Sie diese %ddurch etwas wie %.2f.


6

Simple-r ist die Antwort:

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

Es verwendet R-Umgebung, um die statistische Analyse zu vereinfachen.


5

Nur um eine Vielzahl von Optionen auf dieser Seite vorzustellen, gibt es zwei weitere Möglichkeiten:

1: Oktave

  • GNU Octave ist eine hochinterpretierte Sprache, die hauptsächlich für numerische Berechnungen gedacht ist. Es bietet Möglichkeiten zur numerischen Lösung linearer und nichtlinearer Probleme sowie zur Durchführung anderer numerischer Experimente.

Hier ist ein kurzes Oktavbeispiel.

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 + einzweckwerkzeuge .

Dieses Skript verwendet numprocessund numaveragefrom package, damit bash Gleitkommazahlen verarbeitetnum-utils .

PS. Ich habe auch einen vernünftigen Blick darauf geworfen bc, aber für diesen speziellen Job bietet es nichts, was über das hinausgeht, was es awktut. Es ist (wie das 'c' in 'bc' angibt) ein Taschenrechner - ein Taschenrechner, der viel Programmierung erfordert, awkund dieses Bash-Skript ...


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

Ich werde Lesmanas Wahl von R überholen und mein erstes R-Programm anbieten. Es liest eine Zahl pro Zeile in der Standardeingabe und schreibt vier durch Leerzeichen getrennte Zahlen (min, max, average, median) in die Standardausgabe.

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

Vielen Dank für die "Sekunde" (beruhigend) ... Ihr Beispiel war nützlich, da ich nicht direkt erkannt habe, dass Res sich um die interaktive Schnittstelle handelt, und Rscriptdie Skriptdateien steuert, die gemäß Ihrem Beispiel-Hash-Bang ausführbar sein können , oder innerhalb eines Bash-Skripts aufgerufen. Die Skripts können Befehlszeilenargumente verarbeiten (z. B. stackoverflow.com/questions/2045706/… ), sodass sie gut aussehen ... Auch R-Ausdrücke können in Bash über -e... verwendet werden Ich frage mich, wie im RVergleich zu bc...
Peter.O

2

Das Folgende sort/ awkTandem macht es:

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

(Der Median wird als Mittelwert der beiden zentralen Werte berechnet, wenn die Anzahl der Werte gerade ist.)


2

In Anlehnung an den Code von Bruce wird hier eine effizientere Implementierung vorgestellt, bei der nicht alle Daten im Speicher bleiben. Wie in der Frage angegeben, wird davon ausgegangen, dass die Eingabedatei (höchstens) eine Nummer pro Zeile enthält. Es werden die Zeilen in der Eingabedatei gezählt, die eine qualifizierende Nummer enthalten, und die Zählung wird awkzusammen mit den sortierten Daten (vor) dem Befehl übergeben. So zum Beispiel, wenn die Datei enthält

6.0
4.2
8.3
9.5
1.7

dann ist die Eingabe awktatsächlich

5
1.7
4.2
6.0
8.3
9.5

Dann awkerfasst das Skript die Datenanzahl im NR==1Codeblock und speichert den Mittelwert (oder die beiden Mittelwerte, die gemittelt werden, um den Median zu ergeben), wenn sie angezeigt werden.

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

Willkommen bei Unix & Linux! Gute Arbeit für einen ersten Beitrag. (1) Während dies die Frage beantworten mag, wäre es eine bessere Antwort, wenn Sie erklären könnten, wie / warum dies geschieht. Die Standards des Standorts haben sich in den letzten vier Jahren weiterentwickelt. Während Antworten nur mit Code im Jahr 2011 akzeptabel waren, bevorzugen wir jetzt umfassende Antworten, die mehr Erklärung und Kontext bieten. Ich bitte Sie nicht, das gesamte Drehbuch zu erklären. nur die Teile, die Sie geändert haben (aber wenn Sie das gesamte Skript erklären möchten, ist das auch in Ordnung). (Übrigens, ich verstehe es gut; ich frage im Namen unserer weniger erfahrenen Benutzer.)… (Fortsetzung)
G-Man

(Fortsetzung)… Bitte antworten Sie nicht in Kommentaren; Bearbeiten Sie Ihre Antwort, um sie klarer und vollständiger zu gestalten. (2) Das Skript so zu reparieren, dass nicht das gesamte Array gespeichert werden muss, ist eine gute Verbesserung, aber ich bin nicht sicher, ob es angebracht ist, zu sagen, dass Ihre Version "effizienter" ist, wenn Sie drei unnötige catBefehle haben. siehe UUOC . … (Fortsetzung)
G-Man

(Fortsetzung)… (3) Ihr Code ist sicher, da Sie festgelegt haben FILENAMEund wissen, auf was Sie ihn festgelegt haben. Im Allgemeinen sollten Sie jedoch immer Shell-Variablen angeben, es sei denn, Sie haben einen guten Grund, dies nicht zu tun Sicher, Sie wissen, was Sie tun. (4) Sowohl Ihre Antwort als auch die von Bruce ignorieren negative Eingaben (dh Zahlen, die mit beginnen -); Es gibt nichts in der Frage, was darauf hindeutet, dass dies das richtige oder gewünschte Verhalten ist. Fühle dich nicht schlecht; Es ist über vier Jahre her und anscheinend bin ich die erste Person, die es bemerkt hat.
G-Man

Änderungen gemäß den Vorschlägen vorgenommen. Wusste nicht, wie hoch der Aufwand für das Kommando für Katzen war. Immer verwendet, um einzelne Dateien zu streamen. Vielen Dank, dass Sie mir von UUOC erzählt haben ...
Rahul Agarwal

Gut. Ich habe die dritte gestrichen catund der Erklärung hinzugefügt.
G-Man

2

Das numist eine winzige awkHülle, die genau dies und mehr leistet, z

$ 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

Es erspart Ihnen die Neuerfindung des Rades in der ultra-portablen awk. Die Dokumente sind oben angegeben und der direkte Link hier (siehe auch die GitHub-Seite ).


Links zu verdecktem Webcode, der auf dem Benutzercomputer ausgeführt werden soll, erscheinen mir als eine schlechte Idee. Die Site, die den Code enthält, befindet sich hier

Wo wurde diese „battletested“ Code gehostet , bevor sie auf gesetzt wird Github alle 4 Monate her? Ich finde es extrem verdächtig, dass der Link zu Github vom Curl-Download-Befehl abgezogen werden muss. Es ist viel einfacher herauszufinden, wie man dem Entwickler finanziell spenden kann. Es sieht so aus, als ob der Autor dieses Codes befürchtet, die Leute könnten zum Github gehen und sich die (fast nicht existierende) Geschichte und Statistik ansehen. Gibt es einen Grund, diesen Kampf überhaupt als erprobt zu bezeichnen, abgesehen von dem Versuch, Geld zu sammeln?
Anthon

@ BinaryZeba: aktualisiert
coderofsalvation

@Anthon ok, den 'battletested'-Teil entfernt. Ich glaube nicht, dass dies der Ort für Verschwörung ist.
coderofsalvation

2

Mit 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/pythonEinzige Lösung - kein Leereingabesicher!

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

Sie haben den Median
Peter.O

@ Peter.O behoben.
Ravwojdyla

Für das Statistikmodul ist eine Python-Version> = 3.4
Peter.O

@ Peter.O du hast recht - ist das ein problem?
Ravwojdyla

Es ist kein Problem, es sei denn, Sie haben nicht die entsprechende Python-Version. Es macht es nur weniger portabel.
Peter.O

0

Wenn Sie sich mehr für Nützliches als für Coolness oder Cleveres interessieren, perlist die Wahl einfacher als awk. Im Großen und Ganzen wird es auf jedem * nix mit konsistentem Verhalten sein und ist einfach und kostenlos unter Windows zu installieren. Ich denke, es ist auch weniger kryptisch als awk, und es wird einige Statistikmodule geben, die Sie verwenden könnten, wenn Sie zwischen dem Schreiben selbst und etwas wie R ein Halfway-House wollen Es perldauerte ungefähr eine Minute, bis das Skript geschrieben war, und ich schätze, der einzige kryptische Teil wäre derwhile(<>) , der eine sehr nützliche Abkürzung darstellt diese Zeile in der speziellen Variablen$_. Sie können dies also in eine Datei mit dem Namen count.pl einfügen und als ausführen perl count.pl myfile. Abgesehen davon sollte es schmerzlich offensichtlich sein, was los ist.

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

3
Sie haben den Median
Peter.O

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

Diese Antwort wäre nützlich, wenn erklärt werden würde, wie der obige Code die Frage beantwortet. Sie sollten z. B. sagen, dass Bash (nicht sh) als Interpreter verwendet wird. Es gibt auch ein Problem damit, wie die Daten aus der Datei in das Array eingelesen werden.
Anthony Geoghegan
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.