is there a way to sum column values in a standard table?
Yes, there are just two things to make sure:
- you need to store the data somewhere before creating the table
- 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
)
Thanks a lot.
I will try to adapt to my tables
OK I will look. Thank a lots
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.