10

This is a direct follow-up of https://tex.stackexchange.com/questions/62409. Jake showed how to create a contingency table with the pgfplotstable package (and booktabs). Integer size limitations hit pretty fast and I was wondering if there was a workaround or another recommended approach to deal with this.

The MWE example below has 3 example datasets: the first example has small integers and works, the second example has 'medium-size' integers and fails at the computation of the totals, and the third example has 'large' integers and fails everywhere. I insert a screenshot for each.

I made a lame attempt at wrapping the calls with \pgfkeys{/pgf/fpu}, but did not succeed. My workaround was to use units that made the integers small, e.g. displaying thousands.

\documentclass[border=5pt]{standalone}
\usepackage{booktabs}
\usepackage{pgfplots}
\usepackage{pgfplotstable}
\pgfplotstableset{col sep=comma}% needed here too...

%% Jake @ https://tex.stackexchange.com/questions/62409
\newcommand{\createcontingencytable}[4]{ %
% #1=table name
% #2=first column name
% #3=new row sum name
% #4=new column sum name
\pgfplotstablecreatecol[
    create col/assign/.code={% In each row ... 
        \def\rowsum{0}
        \pgfmathtruncatemacro\maxcolindex{\pgfplotstablecols-1}
        % ... loop over all columns, summing up the elements
        \pgfplotsforeachungrouped \col in {1,...,\maxcolindex}{
            \pgfmathsetmacro\rowsum{\rowsum+\thisrowno{\col}}
        }
        \pgfkeyslet{/pgfplots/table/create col/next content}\rowsum
    }
]{#3}{#1}%
%
% Transpose the table, so we can repeat the summation step for the columns
\pgfplotstabletranspose[colnames from={#2},input colnames to={#2}]{\intermediatetable}{#1}
%
% Sums for each column
\pgfplotstablecreatecol[
    create col/assign/.code={%
        \def\colsum{0}
        \pgfmathtruncatemacro\maxcolindex{\pgfplotstablecols-1}
        \pgfplotsforeachungrouped \col in {1,...,\maxcolindex}{
            \pgfmathsetmacro\colsum{\colsum+\thisrowno{\col}}
        }
        \pgfkeyslet{/pgfplots/table/create col/next content}\colsum
    }
]{#4}\intermediatetable
%
% Transpose back to the original form
\pgfplotstabletranspose[colnames from=#2, input colnames to=#2]{\contingencytable}{\intermediatetable}
}
%

%% Load data
\usepackage{filecontents}% for self-contained example
%% Example 1: OK
\begin{filecontents*}{data1.csv}
Duration,France,Germany
A,10,90
B,20,80
C,30,70
D,40,60
E,50,50
\end{filecontents*}

%% Example 2: Data OK, but Total too large
\begin{filecontents*}{data2.csv}
Duration,France,Germany
A,1000,9000
B,2000,8000
C,3000,7000
D,4000,6000
E,5000,5000
\end{filecontents*}

%% Example 3: Data too large
\begin{filecontents*}{data3.csv}
Duration,France,Germany
A,209207,31522
B,309251,68742
C,257560,90127
D,257613,125882
E,723154,813889
\end{filecontents*}

% Play around with data1.csv, data2.csv, and data3.csv
\pgfplotstableread{data1.csv}{\mydata} 

%% Create the contingency table
\createcontingencytable{\mydata}{Duration}{Total}{Total}

\begin{document}
\pgfplotstabletypeset[
    col sep = comma,
    columns = {Duration, France, Germany, Total},
    columns/Duration/.style = {string type},
]\contingencytable
\end{document}

Example 1: Small Integers

Example 2: Medium Integers

Example 3: Large Integers

Bonus Track [EDITED]: m̶y̶ ̶w̶o̶r̶k̶a̶r̶o̶u̶n̶d̶ the finished product, thanks to to great answers!

    \documentclass[border=5pt]{standalone}
    \usepackage{booktabs}
    \usepackage[svgnames,table]{xcolor}% \rowcolors
    \usepackage{pgfplots}
    \usepackage{pgfplotstable}

    \pgfplotstableset{% pgfplotstable options
        col sep = comma,
        every head row/.style = {before row = \toprule, after row=\midrule},
        every last row/.style = {after row=\bottomrule},
    }

    \pgfkeys{% \pgfkeys provides \pgfmathprintnumber[display]{} without pgfplots
        /pgf/number format/.cd,
        fixed,
        fixed zerofill,
        precision=0,
        set thousands separator={,},
    }

    %% Jake @ https://tex.stackexchange.com/questions/62409
    \newcommand{\createcontingencytable}[4]{ %
    % #1=table name
    % #2=first column name
    % #3=new row sum name
    % #4=new column sum name
    \pgfkeys{/pgf/fpu=true,/pgf/fpu/output format=fixed}% handle large integers
    \pgfplotstablecreatecol[
        create col/assign/.code={% In each row ... 
            \def\rowsum{0}
            \pgfmathtruncatemacro\maxcolindex{\pgfplotstablecols-1}
            % ... loop over all columns, summing up the elements
            \pgfplotsforeachungrouped \col in {1,...,\maxcolindex}{
                \pgfmathsetmacro\rowsum{\rowsum+\thisrowno{\col}}
            }
            \pgfkeyslet{/pgfplots/table/create col/next content}\rowsum 
        }
    ]{#3}{#1}%
    %
    % Transpose the table, so we can repeat the summation step for the columns
    \pgfplotstabletranspose[colnames from={#2},input colnames to={#2}]{\intermediatetable}{#1}
    %
    % Sums for each column
    \pgfplotstablecreatecol[
        create col/assign/.code={%
            \def\colsum{0}
            \pgfmathtruncatemacro\maxcolindex{\pgfplotstablecols-1}
            \pgfplotsforeachungrouped \col in {1,...,\maxcolindex}{
                \pgfmathsetmacro\colsum{\colsum+\thisrowno{\col}}
            }
            \pgfkeyslet{/pgfplots/table/create col/next content}\colsum
        }
    ]{#4}\intermediatetable
    %
    % Transpose back to the original form
    \pgfplotstabletranspose[colnames from=#2, input colnames to=#2]{\contingencytable}{\intermediatetable}
    \pgfkeys{/pgf/fpu=false}% Stop using fpu library
    }
    %


    %% Load data and create the contingency table
    \begin{filecontents*}{data4.csv}
    Duration,France,Germany
    A,209,32
    B,309,69
    C,258,90
    D,258,126
    E,723,814
    \end{filecontents*}
    \begin{filecontents*}{data5.csv}
    Duration,France,Germany
    A,209207,31522
    B,309251,68742
    C,257560,90127
    D,257613,125882
    E,723154,813889
    \end{filecontents*}
    \pgfplotstableread{data5.csv}{\mydata} 
    \createcontingencytable{\mydata}{Duration}{Total}{Total}

    \begin{document}
    \rowcolors{1}{}{gray!10}%
    \renewcommand{\tabcolsep}{20pt}%
    \renewcommand{\arraystretch}{1.5}%
    \pgfplotstabletypeset[
        every head row/.style = {%
            before row={\toprule  & \multicolumn{3}{c}{Unemployment (Thousands)}\\ \cmidrule{2-4}}},
        columns = {Duration, France, Germany, Total},
        columns/Duration/.style = {string type},
        columns/Duration/.append style = {string replace={A}{$<$~1 month}},
        columns/Duration/.append style = {string replace={B}{$>$~1 month and $<$~3 months}},
        columns/Duration/.append style = {string replace={C}{$>$~3 month and $<$~6 months}},
        columns/Duration/.append style = {string replace={D}{$>$~6 month and $<$~1 year}},
        columns/Duration/.append style = {string replace={E}{$1$~year and over}},
        columns/Duration/.append style = {column type = {l}},
    ]\contingencytable
    \end{document}

enter image description here

10
+500

Switching pgf's FPU on solves the dimension too large problem, as expected. The problem is that the FPU has a weird representation for floating point numbers. For example 1Y1.2e3] is the internal representation of 1200 in the FPU.

The number before the Y is a flag that says if the number is positive, or negative, if it is a NaN, or infinity. Then comes the number and a ], possibly to make it match in a delimited macro.

And this internal representation apparently is not understood by \pgfmathparse.

The solution is to set the /pgf/fpu/output format to fixed, and everything seems to work.

Data1: enter image description here

Data2: enter image description here

Data3: enter image description here

\documentclass[border=5pt,convert]{standalone}
\usepackage{booktabs}
\usepackage{pgfplots}
\usepackage{pgfplotstable}
\pgfplotstableset{col sep=comma}% needed here too...

%% Jake @ https://tex.stackexchange.com/questions/62409
\newcommand{\createcontingencytable}[4]{ %
% #1=table name
% #2=first column name
% #3=new row sum name
% #4=new column sum name
\pgfkeys{/pgf/fpu=true,/pgf/fpu/output format=fixed}% <----- HERE
\pgfplotstablecreatecol[
    create col/assign/.code={% In each row ... 
        \def\rowsum{0}
        \pgfmathtruncatemacro\maxcolindex{\pgfplotstablecols-1}
        % ... loop over all columns, summing up the elements
        \pgfplotsforeachungrouped \col in {1,...,\maxcolindex}{
            \pgfmathsetmacro\rowsum{\rowsum+\thisrowno{\col}}
        }
        \pgfkeyslet{/pgfplots/table/create col/next content}\rowsum
    }
]{#3}{#1}%
%
% Transpose the table, so we can repeat the summation step for the columns
\pgfplotstabletranspose[colnames from={#2},input colnames to={#2}]{\intermediatetable}{#1}
%
% Sums for each column
\pgfplotstablecreatecol[
    create col/assign/.code={%
        \def\colsum{0}
        \pgfmathtruncatemacro\maxcolindex{\pgfplotstablecols-1}
        \pgfplotsforeachungrouped \col in {1,...,\maxcolindex}{
            \pgfmathsetmacro\colsum{\colsum+\thisrowno{\col}}
        }
        \pgfkeyslet{/pgfplots/table/create col/next content}\colsum
    }
]{#4}\intermediatetable
%
% Transpose back to the original form
\pgfplotstabletranspose[colnames from=#2, input colnames to=#2]{\contingencytable}{\intermediatetable}
\pgfkeys{/pgf/fpu=false}% <----- AND HERE
}
%

%% Load data
\usepackage{filecontents}% for self-contained example
%% Example 1: OK
\begin{filecontents*}{data1.csv}
Duration,France,Germany
A,10,90
B,20,80
C,30,70
D,40,60
E,50,50
\end{filecontents*}

%% Example 2: Data OK, but Total too large
\begin{filecontents*}{data2.csv}
Duration,France,Germany
A,1000,9000
B,2000,8000
C,3000,7000
D,4000,6000
E,5000,5000
\end{filecontents*}

%% Example 3: Data too large
\begin{filecontents*}{data3.csv}
Duration,France,Germany
A,209207,31522
B,309251,68742
C,257560,90127
D,257613,125882
E,723154,813889
\end{filecontents*}

% Play around with data1.csv, data2.csv, and data3.csv
\pgfplotstableread{data1.csv}{\mydata} 

%% Create the contingency table
\createcontingencytable{\mydata}{Duration}{Total}{Total}

\begin{document}
\pgfplotstabletypeset[
    col sep = comma,
    columns = {Duration, France, Germany, Total},
    columns/Duration/.style = {string type},
]\contingencytable
\end{document}
  • Excellent, thanks! The following in the preamble will typeset all numbers in the table to my preferred format. \pgfkeys{/pgf/number format/.cd, fixed, fixed zerofill, precision=0, set thousands separator={,},} – PatrickT Mar 9 '18 at 5:53
  • Note: Must remember to set that /pgf/fpu/output format option to fixed! Great find. – PatrickT Mar 9 '18 at 9:30
  • 1
    @PatrickT Just for the record, I forgot to mention: /pgf/fpu/output format=sci works too. I just didn't see a difference :P – Phelype Oleinik Mar 9 '18 at 13:08
7
+500

For calculations with integers smaller than about 2*10^9, there is no need for using a FPU. You can use eTeXs \numexpr instead, which is probably faster:

\documentclass[border=5pt]{standalone}
\usepackage{booktabs}
\usepackage{pgfplots}
\usepackage{pgfplotstable}
\pgfplotstableset{col sep=comma}% needed here too...

%% Jake @ https://tex.stackexchange.com/questions/62409
\newcommand{\createcontingencytable}[4]{ %
% #1=table name
% #2=first column name
% #3=new row sum name
% #4=new column sum name
\pgfplotstablecreatecol[
    create col/assign/.code={% In each row ... 
        \def\rowsum{0}
        \pgfmathtruncatemacro\maxcolindex{\pgfplotstablecols-1}
        % ... loop over all columns, summing up the elements
        \pgfplotsforeachungrouped \col in {1,...,\maxcolindex}{
          \edef\rowsum{\number\numexpr\rowsum+\thisrowno{\col}\relax}
        }
        \pgfkeyslet{/pgfplots/table/create col/next content}\rowsum
    }
]{#3}{#1}%
%
% Transpose the table, so we can repeat the summation step for the columns
\pgfplotstabletranspose[colnames from={#2},input colnames to={#2}]{\intermediatetable}{#1}
%
% Sums for each column
\pgfplotstablecreatecol[
    create col/assign/.code={%
        \def\colsum{0}
        \pgfmathtruncatemacro\maxcolindex{\pgfplotstablecols-1}
        \pgfplotsforeachungrouped \col in {1,...,\maxcolindex}{
          \edef\colsum{\number\numexpr\colsum+\thisrowno{\col}\relax}
        }
        \pgfkeyslet{/pgfplots/table/create col/next content}\colsum
    }
]{#4}\intermediatetable
%
% Transpose back to the original form
\pgfplotstabletranspose[colnames from=#2, input colnames to=#2]{\contingencytable}{\intermediatetable}
}
%

%% Load data
\usepackage{filecontents}% for self-contained example
%% Example 1: OK
\begin{filecontents*}{data1.csv}
Duration,France,Germany
A,10,90
B,20,80
C,30,70
D,40,60
E,50,50
\end{filecontents*}

%% Example 2: Data OK, but Total too large
\begin{filecontents*}{data2.csv}
Duration,France,Germany
A,1000,9000
B,2000,8000
C,3000,7000
D,4000,6000
E,5000,5000
\end{filecontents*}

%% Example 3: Data too large
\begin{filecontents*}{data3.csv}
Duration,France,Germany
A,209207,31522
B,309251,68742
C,257560,90127
D,257613,125882
E,723154,813889
\end{filecontents*}

% Play around with data1.csv, data2.csv, and data3.csv
\pgfplotstableread{data3.csv}{\mydata} 

%% Create the contingency table
\createcontingencytable{\mydata}{Duration}{Total}{Total}

\begin{document}
\pgfplotstabletypeset[
    col sep = comma,
    columns = {Duration, France, Germany, Total},
    columns/Duration/.style = {string type},
]\contingencytable
\end{document}

The tables

  • Thanks Marcel! Yours and Phelype's answer both solve my problem. I selected his answer only because it came first (a difficult choice to make). – PatrickT Mar 9 '18 at 5:55
  • As noted by David Carlisle, "LaTeX releases from 2015 onwards do detect if they are running on etex (or xetex or luatex) and use the full extended register range by default." Thus, the etex package does not even need to be loaded in the preamble. Great. [see: tex.stackexchange.com/questions/186588/… – PatrickT Mar 9 '18 at 9:24
  • 1
    This doesn't make any use of the extended register range, the only used etex feature is \numexpr, so even with older LaTeX versions loading the etex package would not be necessary. – Marcel Krüger Mar 10 '18 at 7:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.