Apakah ada cara untuk mendapatkan min, maks, median, dan rata-rata daftar angka dalam satu perintah?

93

Saya memiliki daftar angka dalam file, satu per baris. Bagaimana saya bisa mendapatkan nilai minimum, maksimum, median dan rata - rata ? Saya ingin menggunakan hasil dalam skrip bash.

Meskipun situasi langsung saya adalah untuk bilangan bulat, solusi untuk angka floating-point akan berguna di telepon, tetapi metode bilangan bulat sederhana baik-baik saja.

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

Jawaban:

50

Anda dapat menggunakan bahasa pemrograman R .

Berikut ini adalah skrip R cepat dan kotor:

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

Perhatikan "stdin"di scanmana merupakan nama file khusus untuk dibaca dari input standar (artinya dari pipa atau pengalihan).

Sekarang Anda dapat mengarahkan ulang data Anda melalui stdin ke skrip R:

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

Juga berfungsi untuk floating point:

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

Jika Anda tidak ingin menulis file skrip R, Anda dapat memanggil true-liner (dengan linebreak hanya untuk keterbacaan) di baris perintah menggunakan 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

Baca manual R yang bagus di http://cran.r-project.org/manuals.html .

Sayangnya referensi lengkap hanya tersedia dalam PDF. Cara lain untuk membaca referensi adalah dengan mengetikkan ?topicnameprompt sesi R interaktif.


Untuk kelengkapan: ada perintah R yang menampilkan semua nilai yang Anda inginkan dan banyak lagi. Sayangnya dalam format ramah manusia yang sulit diurai secara terprogram.

> 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
sumber
1
Itu terlihat menarik .. Saya akan melihat lebih dekat besok .. Berdasarkan halaman wikipedia, "R telah menjadi standar de facto di antara para ahli statistik" ... yah itu penghargaan yang signifikan ... Saya actaully mencoba untuk memuatnya
Suatu
10
di repo ubuntu (dan debian?) paket diberi nama r-base.
lesmana
terima kasih, saya perlu referensi nama itu :) Saya tidak memikirkan r- di bidang pencarian sinaptik dan itu tidak bekerja pada karakter tunggal ... Saya sudah mencobanya sekarang, dan itu terlihat ideal .. Rbahasa jelas yang terbaik untuk kebutuhan saya dalam situasi ini .. Sesuai jawaban Gilles, Rscriptantarmuka ke file skrip paling tepat (vs. R, yang merupakan antarmuka interaktif) ... dan R di terminal membuat kalkulator yang berguna , atau uji lingkungan (seperti python :)
Peter.O
(+1) Saya suka R. Saya tidak bisa cukup merekomendasikannya.
Dason
6
atau hanyacat datafile | Rscript -e 'print(summary(scan("stdin")));'
shabbychef
52

Saya sebenarnya menyimpan sedikit program awk untuk memberikan jumlah, jumlah data, datum minimum, datum maksimum, rata-rata dan median dari satu kolom data numerik (termasuk angka negatif):

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

Script di atas membaca dari stdin, dan mencetak kolom-kolom output yang dipisahkan tab pada satu baris.

Bruce Ediger
sumber
1
Aha! sudah jelas (sekarang saya telah melihat skrip awk Anda :) ... Tidak perlu untuk terus memeriksa min dan maks ketika array diurutkan :) dan itu berarti bahwa NR==1can go (a-use-of-of-use) jika) bersamaan dengan pemeriksaan min / maks, sehingga semua inisialisasi dapat ditemukan di bagian BEGIN (bagus!) ... Mengizinkan komentar juga merupakan sentuhan yang bagus .. Terima kasih, +1 ...
Peter.O
Hanya pemikiran .. mungkin hanya memperbolehkan angka lebih baik daripada menolak komentar (tapi itu tergantung kebutuhan Anda) ..
Peter.O
1
Secara teknis, awkakan menganggap variabel "baru" adalah nol, jadi dalam hal ini BEGIN{}bagian tersebut tidak perlu. Saya telah memperbaiki bungkusnya (tidak perlu keluar dari jeda baris juga). Saya juga terbiasa OFS="\t"membersihkan printbaris dan menerapkan komentar kedua @ Peter.O. (Ya, regex saya memungkinkan ., tetapi sebagai awkmengartikannya sebagai 0, itu dapat diterima.)
Adam Katz
1
@ AdamKatz - ini adalah perubahan besar, tetapi seperti sudah ada, saya tidak menulis program. awkSkrip saya sekarang jauh berbeda. Saya hampir merasa seperti Anda harus mengambil kredit untuk program di atas, untuk memberikan kredit di mana kredit jatuh tempo.
Bruce Ediger
1
Saya menulis skrip perl yang disebut avg yang melakukan ini dan lebih, omong-omong.
Adam Katz
47

Dengan datamash GNU :

$ printf '1\n2\n4\n' | datamash max 1 min 1 mean 1 median 1
4   1   2.3333333333333 2
cuonglm
sumber
4
jawaban paling sederhana sejauh ini untuk bash, seperti yang diminta
rfabbri
3
brew install datamashmemberi Anda versi yang berfungsi untuk macOS, jika Anda memiliki Hombrew yang diinstal.
Per Lundberg
19

Min, maks dan rata-rata cukup mudah didapat dengan 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

Menghitung median sedikit lebih rumit, karena Anda perlu mengurutkan angka dan menyimpan semuanya dalam memori untuk sementara waktu atau membacanya dua kali (pertama kali menghitungnya, kedua - untuk mendapatkan nilai median). Berikut adalah contoh yang menyimpan semua angka dalam memori:

% 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
sumber
Terima kasih ... teladan Anda adalah langkah awal yang baik untuk awk, bagi saya .. Saya telah men-tweak sedikit dan menyatukan keduanya (mendapatkan nuansa awk) ... Saya menggunakan awk asortdaripada piped sort, dan sepertinya menyortir bilangan bulat dan desimal dengan benar .. Berikut adalah tautan ke versi tempel paste.ubuntu.com/612674 saya ... (Dan catatan untuk Kim: Saya telah bereksperimen dengan awk selama beberapa jam sekarang. (Bekerja dengan contoh minat pribadi adalah cara yang lebih baik bagi saya) ... Catatan umum untuk pembaca: Saya masih tertarik untuk melihat metode lain. semakin kompak semakin baik. Saya akan menunggu beberapa saat ...
Peter.O
17

pythonpy bekerja dengan baik untuk hal seperti ini:

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

Minimum:

jq -s min

Maksimum:

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

Rata-rata:

jq -s add/length

Dalam jqopsi -s( --slurp) buat array untuk jalur input setelah mengurai setiap baris sebagai JSON, atau sebagai nomor dalam kasus ini.

nisetama
sumber
3
Solusi jq layak disebutkan secara khusus, karena ringkas, dan menggunakan kembali alat dengan cara yang tidak jelas.
jplindstrom
1
Cantik! berharap saya bisa memberi +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]};
Tidak
sumber
echo file.txtsepertinya tidak benar, mungkincat
malat
6

Dan liner satu (panjang) Perl, termasuk 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;'

Opsi khusus yang digunakan adalah:

  • -0777 : baca seluruh file sekaligus, bukan baris demi baris
  • -a : autosplit ke dalam array @F

Versi skrip yang lebih mudah dibaca dari hal yang sama adalah:

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

Jika Anda ingin desimal, gantikan %ddengan sesuatu seperti %.2f.

mivk
sumber
6

Simple-r adalah jawabannya:

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

Ia menggunakan lingkungan R untuk menyederhanakan analisis statistik.

pengguna48270
sumber
5

Hanya demi memiliki berbagai opsi yang disajikan pada halaman ini, Berikut adalah dua cara lagi:

1: oktaf

  • GNU Octave adalah bahasa yang ditafsirkan tingkat tinggi, terutama ditujukan untuk perhitungan numerik. Ini memberikan kemampuan untuk solusi numerik masalah linier dan nonlinier, dan untuk melakukan eksperimen numerik lainnya.

Ini adalah contoh oktaf cepat.

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 + alat tujuan tunggal .

Agar bash menangani angka floating-point, skrip ini menggunakan numprocessdan numaveragedari paket num-utils.

PS. Saya juga memiliki pandangan yang masuk akal bc, tetapi untuk pekerjaan khusus ini, ia tidak menawarkan apa pun selain apa yang awkdilakukannya. Ini adalah (seperti 'c' dalam 'bc' menyatakan) kalkulator — kalkulator yang membutuhkan banyak pemrograman awkdan skrip bash ini ...


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
sumber
4

Saya akan memilih lesmana kedua dari R dan menawarkan program R pertama saya. Bunyinya satu angka per baris pada input standar dan menulis empat angka (min, maks, rata-rata, median) yang dipisahkan oleh spasi ke output standar.

#!/usr/bin/env Rscript
a <- scan(file("stdin"), c(0), quiet=TRUE);
cat(min(a), max(a), mean(a), median(a), "\n");
Gilles
sumber
Terima kasih untuk "kedua" (meyakinkan) ... contoh Anda bermanfaat, karena saya tidak langsung menyadari bahwa itu Radalah antarmuka interaktif, dan Rscriptmendorong file skrip, yang dapat dieksekusi sesuai contoh Anda hash-bang , atau dipanggil dari dalam skrip bash .. Skrip dapat menangani args baris perintah (mis. stackoverflow.com/questions/2045706/... ) sehingga terlihat bagus ... Juga ekspresi R dapat digunakan dalam bash melalui -e... tetapi Saya heran bagaimana jika Rdibandingkan dengan bc...
Peter.O
2

Di bawah sort/ awktandem melakukannya:

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

(Ini menghitung median sebagai rata-rata dari dua nilai pusat jika jumlah nilai genap)

mik
sumber
2

Mengambil petunjuk dari kode Bruce, berikut ini adalah implementasi yang lebih efisien yang tidak menyimpan seluruh data dalam memori. Sebagaimana dinyatakan dalam pertanyaan, diasumsikan bahwa file input memiliki (paling banyak) satu nomor per baris. Itu menghitung baris dalam file input yang berisi angka yang memenuhi syarat dan melewati hitungan ke awkperintah bersama dengan (sebelumnya) data yang diurutkan. Jadi, misalnya, jika file tersebut berisi

6.0
4.2
8.3
9.5
1.7

maka input ke awksebenarnya

5
1.7
4.2
6.0
8.3
9.5

Kemudian awkskrip menangkap jumlah data dalam NR==1blok kode dan menyimpan nilai tengah (atau dua nilai tengah, yang dirata-rata untuk menghasilkan median) ketika melihatnya.

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
sumber
Selamat datang di Unix & Linux! Pekerjaan bagus untuk posting pertama. (1) Meskipun ini dapat menjawab pertanyaan, itu akan menjadi jawaban yang lebih baik jika Anda bisa menjelaskan bagaimana / mengapa ia melakukannya. Standar situs telah berkembang selama empat tahun terakhir; sementara jawaban hanya kode dapat diterima pada tahun 2011, kami sekarang lebih suka jawaban komprehensif yang memberikan lebih banyak penjelasan dan konteks. Saya tidak meminta Anda untuk menjelaskan seluruh naskah; hanya bagian-bagian yang Anda ubah (tetapi jika Anda ingin menjelaskan keseluruhan skrip, tidak masalah juga). (BTW, saya mengerti itu baik-baik saja; Saya meminta atas nama pengguna kami yang kurang berpengalaman.) ... (Lanjutan)
G-Man
(Lanjutkan) ... Tolong jangan menanggapi dalam komentar; edit jawaban Anda untuk membuatnya lebih jelas dan lebih lengkap. (2) Memperbaiki skrip sehingga tidak perlu menahan seluruh array dalam memori adalah perbaikan yang baik, tetapi saya tidak yakin apakah pantas untuk mengatakan bahwa versi Anda "lebih efisien" ketika Anda memiliki tiga catperintah yang tidak perlu ; lihat UUOC . … (Lanjutan)
G-Man
(Lanjutan) ... (3) Kode Anda aman, karena Anda menetapkan FILENAMEdan Anda tahu apa yang Anda tetapkan, tetapi, secara umum, Anda harus selalu mengutip variabel shell kecuali Anda memiliki alasan yang baik untuk tidak melakukannya, dan Anda yakin Anda tahu apa yang Anda lakukan. (4) Baik jawaban Anda maupun Bruce mengabaikan input negatif (yaitu, angka yang dimulai dengan -); tidak ada dalam pertanyaan yang menyarankan bahwa ini adalah perilaku yang benar atau yang diinginkan. Jangan merasa buruk; sudah lebih dari empat tahun, dan, rupanya, saya orang pertama yang memperhatikan.
G-Man
Edit sesuai saran. Tidak tahu tentang overhead perintah kucing. Selalu menggunakannya untuk melakukan streaming file tunggal. Terima kasih telah memberi tahu saya tentang UUOC .....
Rahul Agarwal
Baik. Saya menghilangkan yang ketiga catdan menambahkan penjelasan.
G-Man
2

Ini numadalah awkpembungkus kecil yang persis melakukan ini dan banyak lagi, misalnya

$ 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

itu menyelamatkan Anda dari menciptakan kembali roda di awk ultra-portabel. Dokumen diberikan di atas, dan tautan langsung di sini (periksa juga halaman GitHub ).

koderofsalvasi
sumber
Tautan ke kode web yang tidak jelas untuk dieksekusi di komputer pengguna bagi saya sepertinya ide yang buruk. Situs yang berisi kode tersebut berada di sini
Di mana ini "battletested" kode host sebelum dimasukkan pada github semua 4 bulan yang lalu? Saya merasa sangat curiga bahwa tautan ke github harus dikupas dari perintah unduhan curl. Adalah jauh lebih mudah untuk mengetahui bagaimana menyumbang secara finansial kepada pengembang. Sepertinya pembuat kode itu takut orang akan pergi ke github dan melihat sejarah dan statistik (hampir tidak ada). Apakah ada alasan untuk menyebut pertarungan ini teruji sama sekali, selain mencoba mengumpulkan uang?
Anthon
@ Binzeba: diperbarui
coderofsalvation
@Anthon ok, menghapus bagian 'battletested'. Saya tidak berpikir ini adalah tempat untuk konspirasi FUD.
coderofsalvation
2

Dengan 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
sumber
1

cat/pythonsatu-satunya solusi - bukan bukti input kosong!

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
sumber
Anda belum menunjukkan median
Peter.O
@ Peter.O diperbaiki.
ravwojdyla
The statistik modul memerlukan versi python> = 3,4
Peter.O
@ Peter.O Anda benar - apakah itu masalah?
ravwojdyla
Ini bukan masalah kecuali Anda tidak memiliki versi python yang sesuai. Itu hanya membuatnya kurang portabel.
Peter.O
0

Jika Anda lebih tertarik pada utilitas daripada menjadi keren atau pintar, maka itu perladalah pilihan yang lebih mudah daripada awk. Pada umumnya ini akan ada di setiap * nix dengan perilaku konsisten, dan mudah dan gratis untuk menginstal di windows. Saya pikir itu juga kurang samar daripada awk, dan akan ada beberapa modul statistik yang dapat Anda gunakan jika Anda ingin setengah jalan antara menulis sendiri dan sesuatu seperti R. Saya cukup belum diuji (sebenarnya saya tahu itu memiliki bug tetapi berfungsi untuk tujuan saya ) perlskrip membutuhkan waktu sekitar satu menit untuk menulis, dan saya kira satu-satunya bagian rahasia akan menjadi while(<>), yang merupakan singkatan yang sangat berguna, yang berarti mengambil file yang dilewatkan sebagai argumen baris perintah, membaca satu baris pada satu waktu dan meletakkan baris dalam variabel khusus$_. Jadi Anda bisa meletakkan ini dalam file bernama count.pl dan menjalankannya sebagai perl count.pl myfile. Terlepas dari itu, jelas sekali menyakitkan apa yang terjadi.

$max = 0;
while (<>) {
 $sum = $sum + $_;
 $max = $_ if ($_ > $max);
 $count++;
}
$avg=$sum/$count;
print "$count numbers total=$sum max=$max mean=$avg\n";
iain
sumber
3
Anda belum menunjukkan median
Peter.O
0
function median()
{
    declare -a nums=($(cat))
    printf '%s\n' "${nums[@]}" | sort -n | tail -n $((${#nums[@]} / 2 + 1)) | head -n 1
}  
David McLaughlin
sumber
Jawaban ini akan berguna jika ada penjelasan tentang bagaimana kode di atas menjawab pertanyaan, misalnya, Anda harus mengatakan bahwa itu menggunakan Bash (bukan sh) sebagai penerjemah. Ada juga masalah dengan bagaimana data dibaca ke dalam array dari file.
Anthony Geoghegan