Existe uma maneira de obter o min, max, median e average de uma lista de números em um único comando?

88

Eu tenho uma lista de números em um arquivo, um por linha. Como posso obter os valores mínimo, máximo, mediano e média ? 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 de inteiro simples é bom.

    
por Peter.O 25.05.2011 / 06:42

18 respostas

48

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 "stdin" em scan , que é um nome de arquivo especial para ler a partir da entrada padrão (isso 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 quer escrever um arquivo de script R, você pode invocar um verdadeiro one-liner (com linebreak apenas para legibilidade) 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 manuais R bem no link .

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

Para completar: há um comando R que gera todos os valores desejados e mais. Infelizmente, em um formato amigável humano que é difícil de analisar programaticamente.

> 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 
    
por 25.05.2011 / 19:07
44

Na verdade, mantenho um pequeno programa awk para fornecer a soma, a contagem de dados, o valor mínimo de referência, o valor máximo de referência, a média e a mediana de uma única coluna de dados numéricos:

#!/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 separadas por tabulações de saída em uma única linha.

    
por 25.05.2011 / 20:07
37

Com GNU datamash :

$ printf '1\n2\n4\n' | datamash max 1 min 1 mean 1 median 1
4   1   2.3333333333333 2
    
por 12.05.2015 / 06:26
18

Min, max e average 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

Calcular mediana é um pouco mais complicado, já que você precisa classificar 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
    
por 25.05.2011 / 10:26
16

pythonpy funciona bem para esse tipo de coisa:

cat file.txt | py --ji -l 'min(l), max(l), numpy.median(l), numpy.mean(l)'
    
por 13.09.2014 / 08:41
16

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

Na opção jq , o -s ( --slurp ) cria uma matriz para as linhas de entrada depois de analisar cada linha como JSON ou como um número nesse caso.

    
por 16.12.2015 / 20:46
6

E um forro de um (longo) de Perl, incluindo 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 vez, em vez de linha por linha
  • -a : autosplite no array @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 %d por algo como %.2f .

    
por 27.05.2015 / 13:07
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.

    
por 01.10.2013 / 03:22
5

Apenas para ter uma variedade de opções apresentadas nesta página, veja mais duas formas:

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 a realização de outros experimentos numéricos.

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 finalidade única .

Para o bash manipular números de ponto flutuante, esse script usa numprocess e numaverage do pacote num-utils .

PS. Eu também tive uma visão razoável de bc , mas para este trabalho em particular, ele não oferece nada além do que awk faz. É (como afirma o 'c' em 'bc') uma calculadora - uma calculadora que requer muita programação como awk e este 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 
    
por 26.05.2011 / 07:02
5
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]};
    
por 07.10.2013 / 17:33
4

Eu vou segundo escolha da lesmana de R e ofereço meu primeiro programa de R. Ele lê um número por linha na entrada padrão e grava quatro números (min, max, average, median) separados por espaços para a 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");
    
por 26.05.2011 / 00:52
2

Tomando sugestões do código de Bruce, aqui está uma implementação mais eficiente que não mantém todos os dados na memória. Como afirmado na pergunta, assume que o arquivo de entrada tem (no máximo) um número por linha. Conta as linhas no arquivo de entrada que contêm um número qualificado e passa a contagem para o comando awk junto com (anterior) os dados classificados. Então, por exemplo, se o arquivo contiver

6.0
4.2
8.3
9.5
1.7

a entrada para awk é, na verdade,

5
1.7
4.2
6.0
8.3
9.5

Em seguida, o script awk captura a contagem de dados no bloco de códigos NR==1 e salva o valor médio (ou os dois valores médios, que são calculados para produzir a mediana) quando os vir.

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
  }
'
    
por 10.10.2015 / 03:44
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
    
por 29.03.2018 / 17:55
1

O sort / awk tandem abaixo 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 a mediana como média dos dois valores centrais se a contagem de valor for par)

    
por 14.05.2015 / 14:59
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 . De um modo geral, será em todos os * nix com comportamento consistente, e é fácil e gratuito para instalar no Windows. Eu acho que também é menos enigmático que awk , e haverá alguns módulos de estatísticas que você poderia usar se você quisesse uma casa intermediária entre escrever você mesmo e algo como R. Meu bastante não testado (na verdade eu sei que tem bugs, mas funciona para meus propósitos) perl script levou cerca de um minuto para escrever, e eu acho que a única parte enigmática seria o while(<>) , que é o muito útil taquigrafia, ou seja, pegue o (s) arquivo (s) passado (s) como argumentos da linha de comando, leia uma linha por vez e coloque essa linha na variável especial $_ . Então você poderia 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";
    
por 27.03.2012 / 14:34
0

cat/python apenas solução - não é uma 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))"
    
por 09.09.2015 / 21:39
0

O num é um pequeno wrapper awk 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

você evita reinventar a roda no awk ultraportátil. Os documentos são fornecidos acima e o link direto aqui (verifique também o < um href="https://github.com/numcommand/num"> página do GitHub ).

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