Existe uma maneira de obter o mínimo, máximo, mediana e média de uma lista de números em um único comando?

93

Eu tenho uma lista de números em um arquivo, um por linha. Como obtenho os valores mínimo, máximo, mediano e médio ? Eu quero usar os resultados em um script bash.

Embora minha situação imediata seja para números inteiros, uma solução para números de ponto flutuante seria útil na linha, mas um método inteiro simples é bom.

Peter.O
fonte

Respostas:

50

Você pode usar a linguagem de programação R .

Aqui está um script R rápido e sujo:

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

Observe o nome do arquivo "stdin"em scanque é especial para ler da entrada padrão (que significa de pipes ou redirecionamentos).

Agora você pode redirecionar seus dados sobre stdin para o script R:

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

Também funciona para pontos flutuantes:

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

Se você não quiser escrever um arquivo de script R, poderá chamar uma linha única (com quebra de linha apenas para facilitar a leitura) na linha de comando usando 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

Leia os bons manuais do R em http://cran.r-project.org/manuals.html .

Infelizmente, a referência completa está disponível apenas em PDF. Outra maneira de ler a referência é digitando ?topicnameo prompt de uma sessão R interativa.


Para completar: existe um comando R que gera todos os valores que você deseja e muito mais. Infelizmente, em um formato amigável para humanos, difícil de analisar de forma programática.

> 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
fonte
11
Parece interessante .. Vou dar uma olhada mais de perto amanhã. Baseado na página da wikipedia, "R se tornou um padrão de fato entre os estatísticos" ... bem, isso é um elogio significativo ... Eu realmente tentei fazer o dowload no outro dia (eu ficava vendo ele mencionou), mas eu não poderia encontrá-lo no repo Ubuntu ... Eu vou segui-lo até amanhã ...
Peter.O
10
no repositório ubuntu (e debian?), o pacote é nomeado r-base.
Lesmana
obrigado, eu precisava dessa referência de nome :) Eu não pensei em r- no campo de pesquisa sináptica e ele não age em um caractere solitário ... Eu tentei agora, e parece ideal .. Ra linguagem é claramente a melhor para o meu requisito nesta situação. Conforme a resposta de Gilles, a Rscriptinterface para arquivos de script é mais apropriada (vs. R, que é a interface interativa) ... e R no terminal é uma calculadora útil ou ambiente de teste (como python :)
Peter.O
(+1) Adoro R. Não posso recomendar o suficiente.
Dason
6
ou apenascat datafile | Rscript -e 'print(summary(scan("stdin")));'
shabbychef
52

Na verdade, eu mantenho um pequeno programa awk para fornecer a soma, contagem de dados, dado mínimo, número máximo, média e mediana de uma única coluna de dados numéricos (incluindo números negativos):

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

O script acima lê de stdin e imprime colunas de saída separadas por tabulação em uma única linha.

Bruce Ediger
fonte
11
Aha! é óbvio (agora que eu vi seu script awk :) ... Não há necessidade de continuar verificando min e max quando a matriz é classificada :) e isso significa que ele NR==1pode continuar (um uso inútil de- if) junto com as verificações min / max, para que toda a inicialização possa ser localizada na seção BEGIN (boa!) ... Permitir comentários também é um toque agradável .. Obrigado, +1 ...
Peter.O
Apenas um pensamento .. talvez permitindo que apenas os valores numéricos é melhor do que não permitir comentários (mas que depende você suas necessidades) ..
Peter.O
11
Tecnicamente, awkassumirá que "novas" variáveis ​​são zero, portanto, neste caso, a BEGIN{}seção é desnecessária. Corrigi o empacotamento (não é necessário escapar das quebras de linha). Eu também costumava OFS="\t"limpar a printlinha e implementou o segundo comentário de @ Peter.O. (Sim, meu regex permite ., mas como awkinterpreta isso como 0, isso é aceitável.)
Adam Katz
11
@ AdamKatz - estas são grandes mudanças, mas como está, eu não escrevi o programa. Meu awkscript agora é substancialmente diferente. Eu quase sinto que você deve receber crédito pelo programa acima, a fim de dar crédito onde o crédito é devido.
precisa
11
Eu escrevi um script perl chamado avg que faz isso e muito mais, a propósito.
Adam Katz
47

Com o GNU datamash :

$ printf '1\n2\n4\n' | datamash max 1 min 1 mean 1 median 1
4   1   2.3333333333333 2
cuonglm
fonte
4
resposta mais simples, de longe, para a festança, como pediu
rfabbri
3
brew install datamashfornece uma versão funcional para o macOS, se você tiver o Hombrew instalado.
Per Lundberg
19

Mín, max e média são muito fáceis de obter com o 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

O cálculo da mediana é um pouco mais complicado, pois você precisa classificar os números e armazená-los todos na memória por um tempo ou lê-los duas vezes (primeira vez para contá-los, segundo - para obter valor mediano). Aqui está um exemplo que armazena todos os números na memória:

% 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
fonte
Obrigado ... seu exemplo é um bom caminho para o awk, para mim .. Eu o ajustei um pouco e juntei os dois (sentindo o awk) ... Eu usei o awk em asortvez do canalizado sorte parece classificar números inteiros e decimais corretamente. Aqui está um link para a minha versão resultante paste.ubuntu.com/612674 ... (E uma observação para Kim: eu estou experimentando o awk há algumas horas Trabalhar com um exemplo de interesse pessoal é muito melhor para mim) ... Uma observação geral para os leitores: Ainda estou interessado em ver outros métodos. quanto mais compacto, melhor. Vou esperar um pouco ...
Peter.O
17

pythonpy funciona bem para esse tipo de coisa:

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

Mínimo:

jq -s min

Máximo:

jq -s max

Mediana:

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

Média:

jq -s add/length

Em jqa -s( --slurpopção) cria uma matriz para as linhas de entrada depois de analisar cada linha como JSON, ou como um número, neste caso.

nisetama
fonte
3
A solução jq merece uma menção especial, pois é sucinta e redefine a ferramenta de uma maneira não óbvia.
Jplindstrom #
11
bela! gostaria de poder dar +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]};
NotANumber
fonte
echo file.txtnão parece muito bem, talvezcat
malat
6

E um liner único (longo) do Perl, incluindo a mediana:

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

As opções especiais usadas são:

  • -0777 : leia o arquivo inteiro de uma só vez em vez de linha por linha
  • -a : divisão automática na matriz @F

Uma versão de script mais legível da mesma coisa seria:

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

Se você quiser decimais, substitua %dpor algo como %.2f.

mivk
fonte
6

Simple-r é a resposta:

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

Ele usa o ambiente R para simplificar a análise estatística.

user48270
fonte
5

Apenas para apresentar uma variedade de opções nesta página, aqui estão mais duas maneiras:

1: oitava

  • O GNU Octave é uma linguagem interpretada de alto nível, destinada principalmente a cálculos numéricos. Ele fornece recursos para a solução numérica de problemas lineares e não lineares e para realizar outras experiências numéricas.

Aqui está um exemplo rápido de oitava.

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 + ferramentas de uso único .

Para o bash manipular números de ponto flutuante, esse script usa numprocesse numaveragefrom package num-utils.

PS. Também tive uma visão razoável bc, mas para esse trabalho em particular, ele não oferece nada além do que awkfaz. É (como o 'c' em 'bc' afirma) uma calculadora - uma calculadora que requer muita programação awke esse 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 
Peter.O
fonte
4

Vou escolher a segunda opção para R de lesmana e oferecer meu primeiro programa de R. Ele lê um número por linha na entrada padrão e grava quatro números (min, max, média, mediana) separados por espaços na saída padrão.

#!/usr/bin/env Rscript
a <- scan(file("stdin"), c(0), quiet=TRUE);
cat(min(a), max(a), mean(a), median(a), "\n");
Gilles
fonte
Obrigado pelo "segundo" (é tranquilizador) ... seu exemplo foi útil, pois eu não percebi diretamente que Ré a interface interativa e Rscriptdirige os arquivos com script, que podem ser executáveis ​​conforme o seu exemplo hash-bang , ou invocado a partir de um script bash. Os scripts podem manipular argumentos da linha de comandos (por exemplo, stackoverflow.com/questions/2045706/… ), para que pareça bom ... Também expressões R podem ser usadas no bash por meio do -e... mas pergunto-me como Rse compara a bc...
Peter.O
2

O abaixo sort/ awktandem faz isso:

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

(calcula mediana como média dos dois valores centrais se a contagem de valores for par)

mik
fonte
2

Tomando dicas do código de Bruce, aqui está uma implementação mais eficiente que não mantém todos os dados na memória. Conforme declarado na pergunta, ele assume que o arquivo de entrada possui (no máximo) um número por linha. Ele conta as linhas no arquivo de entrada que contêm um número qualificado e passa a contagem para o awkcomando junto com (precedendo) os dados classificados. Então, por exemplo, se o arquivo contiver

6.0
4.2
8.3
9.5
1.7

então a entrada para awké realmente

5
1.7
4.2
6.0
8.3
9.5

Em seguida, o awkscript captura a contagem de dados no NR==1bloco de código e salva o valor do meio (ou os dois valores do meio, que são calculados para gerar a mediana) quando os vê.

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
fonte
Bem-vindo ao Unix e Linux! Bom trabalho para um primeiro post. (1) Embora isso possa responder à pergunta, seria uma resposta melhor se você pudesse explicar como / por que isso acontece. Os padrões do site evoluíram nos últimos quatro anos; Embora as respostas somente de código sejam aceitáveis ​​em 2011, agora preferimos respostas abrangentes que fornecem mais explicações e contexto. Não estou pedindo para você explicar o script inteiro; apenas as partes que você alterou (mas se você quiser explicar o script inteiro, tudo bem também). (BTW, eu entendo bem, eu estou pedindo em nome de nossos usuários menos experientes.) ... (Cont)
G-Man
(Continua)… Por favor, não responda nos comentários; edite sua resposta para torná-la mais clara e completa. (2) A correção do script para que ele não precise reter toda a matriz na memória é uma boa melhoria, mas não tenho certeza se é apropriado dizer que sua versão é "mais eficiente" quando você tem três catcomandos desnecessários ; veja UUOC . ... (continua)
G-Man
(Continua)… (3) Seu código é seguro, pois você define FILENAMEe sabe o que definir, mas, em geral, sempre deve citar variáveis ​​de shell, a menos que tenha um bom motivo para não fazê-lo e você Certifique-se que você sabe o que está fazendo. (4) Sua resposta e a de Bruce ignoram dados negativos (ou seja, números começando com -); não há nada na pergunta que sugira que esse seja o comportamento correto ou desejado. Não se sinta mal; já faz mais de quatro anos e, aparentemente, sou a primeira pessoa que percebi.
G-Man
Edições feitas conforme as sugestões. Não sabia sobre a sobrecarga do comando gato. Sempre o utilizava para transmitir arquivos únicos. Obrigado por me contar sobre UUOC .....
Rahul Agarwal
Boa. Eu eliminei o terceiro cate adicionei à explicação.
G-Man
2

O numé um awkinvólucro minúsculo que faz exatamente isso e muito mais, por exemplo

$ 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

evita que você reinvente a roda no awk ultra-portátil. Os documentos são fornecidos acima e o link direto aqui (consulte também a página do GitHub ).

coderofsalvation
fonte
Os links para o código da web obscuro a ser executado no computador do usuário me parecem uma má idéia. O site que contém o código reside aqui
Onde este código "battletested" foi hospedado antes de ser colocado no github todos os 4 meses atrás? Acho extremamente suspeito que o link para o github precise ser removido do comando curl download. É muito mais fácil descobrir como doar financeiramente para o desenvolvedor. Parece que o autor desse código teme que as pessoas acessem o github e olhem a história e as estatísticas (quase inexistentes). Existe alguma razão para considerar essa batalha testada, além de tentar arrecadar dinheiro?
Anthon
@BinaryZeba: updated
coderofsalvation 22/03
@Anton ok, removeu a parte 'battletested'. Eu não acho que este é o lugar para conspiração FUD.
coderofsalvation
2

Com 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
fonte
1

cat/pythonúnica solução - não prova de entrada vazia!

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
fonte
Você não mostrou a mediana
Peter.O
@ Peter.O corrigido.
Ravwojdyla
O estatísticas módulo requer a versão python> = 3,4
Peter.O
@ Peter.O você está correto - isso é um problema?
Ravwojdyla
Não é um problema, a menos que você não tenha a versão python apropriada. Apenas o torna menos portátil.
precisa saber é o seguinte
0

Se você está mais interessado em utilidade do que em ser legal ou inteligente, então perlé uma escolha mais fácil do que awk. Em geral, ele estará em todos os * nix com comportamento consistente e é fácil e gratuito para instalar no Windows. Eu acho que também é menos enigmático do que awk, e haverá alguns módulos de estatísticas que você poderia usar se quisesse uma casa intermediária entre escrevê-lo e algo como R. ) perldemorou cerca de um minuto para escrever, e eu acho que a única parte enigmática seria a while(<>), que é a abreviação muito útil, ou seja, pegar os arquivos passados ​​como argumentos de linha de comando, ler uma linha de cada vez e colocar essa linha na variável especial$_. Então você pode colocar isso em um arquivo chamado count.pl e executá-lo como perl count.pl myfile. Além disso, deve ser dolorosamente óbvio o que está acontecendo.

$max = 0;
while (<>) {
 $sum = $sum + $_;
 $max = $_ if ($_ > $max);
 $count++;
}
$avg=$sum/$count;
print "$count numbers total=$sum max=$max mean=$avg\n";
iain
fonte
3
Você não mostrou a mediana
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
fonte
Essa resposta seria útil se houvesse uma explicação de como o código acima responde à pergunta, por exemplo, você deve dizer que ele está usando o Bash (não sh) como intérprete. Há também um problema com a forma como os dados são lidos na matriz a partir do arquivo.
Anthony Geoghegan