How to sum column values in a table?

is there a way to sum column values in a standard table?

Yes, there are just two things to make sure:

  1. you need to store the data somewhere before creating the table
  2. you need to make sure that you operate on numbers and not content

For demonstration, let’s look at a table you may be starting with:

#table(
  columns: 2,
  align: right,
  stroke: none,
  table.header([Col A], [Col B]),
  table.hline(),
  [1], [2],
  [3], [4],
  [5], [6],
  table.hline(),
  [9],
  [12],
)

This table has three rows of actual data, a header, and manually calculated column sums.

Advice 1 means you should create a variable to store the data before putting it into the table:

#let data = (
  ([1], [2]),
  ([3], [4]),
  ([5], [6]),
)

#table(
  // ...
  table.hline(),
  ..data.flatten(),
  table.hline(),
  // ...
)

data contains an array of arrays – each element is a row, and each row consists of the two columns. This is easier for us to work with, but we need flatten() because table() doesn’t accept this kind of nested array.

Advice 2 means that instead of writing [1], we should just use 1, so that we can actually calculate something:

#let data = (
  (1, 2),
  (3, 4),
  (5, 6),
)

#table(
  // ...
  table.hline(),
  ..data.flatten().map(num => [#num]),
  table.hline(),
  // ...
)

Again, table() can’t simply show numbers, thus I also used map(num => [#num]) to convert each number back into content before displaying.

But by doing this, we’re now ready to actually do calculations with the numbers:

// ...

#let col-a-sum = data.map(row => row.at(0)).sum()
#let col-b-sum = data.map(row => row.at(1)).sum()

#table(
  // ...
  table.hline(),
  ..data.flatten().map(num => [#num]),
  table.hline(),
  [#col-a-sum], [#col-b-sum],
)

This extracts from each row the first or second column, respectively, and sums them up. Then, the numbers are added to the table as content.

In this example, all columns were summed up. To avoid duplication, you could do all columns in a loop like this:

#let col-sums = ()
#for i in array.range(data.first().len()) {
  let col-sum = data.map(row => row.at(i)).sum()
  col-sums.push(col-sum)
}

#table(
  // ...
  ..col-sums.map(num => [#num]),
)

This can be shortened in a few ways; I wanted to show a loop as the main solution because it’s not as terse as these other options, but they do exist:

Shorter sums for all columns
// like the loop before, but using `map()`
// instead of pushing element by element
#let col-sums = array.range(data.first().len()).map(i => {
  data.map(row => row.at(i)).sum()
})

// using a sort of transpose operation to get
// an array of columns from the array of rows
#let col-sums = array.zip(..data).map(array.sum)

As a final example, here’s how to sum up all columns read from a csv file with header:

Table with column sums from CSV
#let csv-data = csv.decode(```
Col A,Col B
1,2
3,4
5,6
```.text)
// When the data come from a file, you'd write this instead:
// #let csv-data = csv("filename.csv")

#let (header, ..data) = csv-data
#let col-sums = array.zip(..data).map(column => column.map(int).sum())

#table(
  columns: 2,
  align: right,
  stroke: none,
  table.header(..header),
  table.hline(),
  ..data.flatten(),
  table.hline(),
  ..col-sums.map(num => [#num]),
)

What’s a bit different here is that instead of having to convert the data to content, it comes out of the csv file as strings. So we don’t need to convert to content (strings work in tables), but do need to convert each number to int before calculating.

Please see tabut package. From " Aggregation using Map and Sum" of their docs:

#import "usd.typ": usd
#import "example-data/titanic.typ": titanic, classes

#table(
  columns: (auto, auto),
  [*Fare, Total:*], [#usd(titanic.map(r => r.Fare).sum())],
  [*Fare, Avg:*], [#usd(titanic.map(r => r.Fare).sum() / titanic.len())], 
  stroke: none
)
1 Like

Thanks a lot.
I will try to adapt to my tables

OK I will look. Thank a lots

1 Like

Given the following code reading data from a csv-File:

#let csv-data = csv.decode(```
Text, Value
"A",2
"B",4.5
"C",6
```.text)

#let (header, ..data) = csv-data
This I have copied from another example.

How can I get the the sum of the variable “Value”. I tried all kind of variations, breaking down much more complex operations, but to no avail.

and re csv:

Is there a problem with doing it this way?

Thank you for the fast answer.

Regrettably #col-b-sum returns 24.56 which is a string concatenation of the values.

I had this result before in one of my myriad attempts.

That’s what the

part is about. If you look at the CSV snippet, it has a column.map(int) in there, to convert the strings into integers. Admittedly, it’s easy to miss when you don’t look what you’re looking for. The complete adapted code (but converting to float, not int) would be this:

#let col-b-sum = data.map(row => float(row.at(1))).sum()

I solved it myself

#let csv-data = csv.decode(```
Text, Value
"A",2
"B",4.5
"C",6
```.text)

#let (header, ..data) = csv-data

#let numeric_data = data.map(row => float(row.at(1))) // convert to float
#let Value_Sum = numeric_data.sum()

#Value_Sum

Sorry, had not seen your answer early enough, more elegant than ‘my’ solution.

Thanks a lot for your assistance.