Legend
Legend

Reputation: 116840

GROUP BY/SUM from shell

I have a large file containing data like this:

a 23
b 8
a 22
b 1

I want to be able to get this:

a 45
b 9

I can first sort this file and then do it in Python by scanning the file once. What is a good direct command-line way of doing this?

Upvotes: 41

Views: 28317

Answers (7)

RavinderSingh13
RavinderSingh13

Reputation: 133528

With sort + awk combination one could try following, without creating array.

sort -k1 Input_file | 
awk '
  prev!=$1 && prev{
    print prev,(prevSum?prevSum:"N/A")
    prev=prevSum=""
  }
  {
    prev=$1
    prevSum+=$2
  }
  END{
    if(prev){
       print prev,(prevSum?prevSum:"N/A")
    }
}'

Explanation: Adding detailed explanation for above.

sort -k1 file1 |                          ##Using sort command to sort Input_file by 1st field and sending output to awk as an input.
awk '                                     ##Starting awk program from here.
  prev!=$1 && prev{                       ##Checking condition prev is NOT equal to first field and prev is NOT NULL.
    print prev,(prevSum?prevSum:"N/A")    ##Printing prev and prevSum(if its NULL then print N/A).
    prev=prevSum=""                       ##Nullify prev and prevSum here.
  }
  {
    prev=$1                               ##Assigning 1st field to prev here.
    prevSum+=$2                           ##Adding 2nd field to prevSum.
  }
  END{                                    ##Starting END block of this awk program from here.
    if(prev){                             ##Checking condition if prev is NOT NULL then do following.
       print prev,(prevSum?prevSum:"N/A") ##Printing prev and prevSum(if its NULL then print N/A).
    }
}'

Upvotes: 2

saaj
saaj

Reputation: 25224

This can be easily achieved with the following single-liner:

cat /path/to/file | termsql "SELECT col0, SUM(col1) FROM tbl GROUP BY col0"

Or.

termsql -i /path/to/file "SELECT col0, SUM(col1) FROM tbl GROUP BY col0"

Here a Python package, termsql, is used, which is a wrapper around SQLite. Note, that currently it's not upload to PyPI, and also can only be installed system-wide (setup.py is a little broken), like:

pip install --user https://github.com/tobimensch/termsql/archive/master.zip

Update

In 2020 version 1.0 was finally uploaded to PyPI, so pip install --user termsql can be used.

Upvotes: 7

shellter
shellter

Reputation: 37288

Edit: The modern (GNU/Linux) solution, as mentioned in comments years ago ;-) .

awk '{
    arr[$1]+=$2
   }
   END {
     for (key in arr) printf("%s\t%s\n", key, arr[key])
   }' file \
   | sort -k1,1

The originally posted solution, based on old Unix sort options:

awk '{
    arr[$1]+=$2
   }
   END {
     for (key in arr) printf("%s\t%s\n", key, arr[key])
   }' file \
   | sort +0n -1

I hope this helps.

Upvotes: 44

Dennis Williamson
Dennis Williamson

Reputation: 360113

This Perl one-liner seems to do the job:

perl -nle '($k, $v) = split; $s{$k} += $v; END {$, = " "; foreach $k (sort keys %s) {print $k, $s{$k}}}' inputfile

Upvotes: 8

Dimitre Radoulov
Dimitre Radoulov

Reputation: 28000

With GNU awk (versions less than 4):

WHINY_USERS= awk 'END {
  for (E in a)
    print E, a[E]
    }
{ a[$1] += $2 }' infile

With GNU awk >= 4:

awk 'END {
  PROCINFO["sorted_in"] = "@ind_str_asc"
  for (E in a)
    print E, a[E]
    }
{ a[$1] += $2 }' infile

Upvotes: 2

Birei
Birei

Reputation: 36262

One way using perl:

perl -ane '
    next unless @F == 2; 
    $h{ $F[0] } += $F[1]; 
    END { 
        printf qq[%s %d\n], $_, $h{ $_ } for sort keys %h;
    }
' infile

Content of infile:

a 23
b 8
a 22
b 1

Output:

a 45
b 9

Upvotes: 2

Charles Duffy
Charles Duffy

Reputation: 295473

No need for awk here, or even sort -- if you have Bash 4.0, you can use associative arrays:

#!/bin/bash
declare -A values
while read key value; do
  values["$key"]=$(( $value + ${values[$key]:-0} ))
done
for key in "${!values[@]}"; do
  printf "%s %s\n" "$key" "${values[$key]}"
done

...or, if you sort the file first (which will be more memory-efficient; GNU sort is able to do tricks to sort files larger than memory, which a naive script -- whether in awk, python or shell -- typically won't), you can do this in a way which will work in older versions (I expect the following to work through bash 2.0):

#!/bin/bash
read cur_key cur_value
while read key value; do
  if [[ $key = "$cur_key" ]] ; then
    cur_value=$(( cur_value + value ))
  else
    printf "%s %s\n" "$cur_key" "$cur_value"
    cur_key="$key"
    cur_value="$value"
  fi
done
printf "%s %s\n" "$cur_key" "$cur_value"

Upvotes: 8

Related Questions