# Function to autoformat string of text

I am writing a thesis that will require the reporting of the results of multiple ANOVAs. Reporting these follows the format F1,16=5.06, P=.009. I would like a tool that does this formatting for me, potentially allowing me to switch between formats for the whole text easily, for example allowing me to change it to F(1,16)=5.06, P=.009, or even F(1,16)=5.06, P<.01 if we're getting fancy. EDIT: For the P<.01 option, an ideal solution would be that the user can add ~3 levels of significance, say, P<.05, P<.01 and P<.001 and the function automatically assigns one of these, based on the reported value.

I guess this would take the format of a custom function that would allow this to work: \reportANOVA{1,16,5.06,.009} with a function that looks something like this

reportANOVA <- function{DFN,DFD,F,P}{
return(F\textsubscript{DFN,DFD}=F, \textit{p}=P)
}


The results of the function need to be within the text.

Is such a thing possible in LaTeX? I have only been using it for a week or so, so I don't know a great deal about it, but obviously similar things are possible in other programming languages (I used R's syntax here).

\documentclass{article}
\usepackage{fixltx2e}

%Function in the preamble like this

reportANOVA <- function{DFN,DFD,F,P}{
return(F\textsubscript{DFN,DFD}=F, \textit{p}=P)
}

\begin{document}

Stats are \reportANOVA{1,16,5.06,.009}

\end{document}


*Obviously this doesn't work.

• Please add a minimal working example (MWE). – cfr Mar 27 '15 at 23:02
• I can't really add a MWE as I don't have a function that works, hence the question. – George Vousden Mar 27 '15 at 23:04
• You can add at least the basic structure of a document. Obviously if you had an answer, you wouldn't need to ask the question. – cfr Mar 27 '15 at 23:10
• I added one, but I think it made the question less clear and didn't include any extra information. – George Vousden Mar 27 '15 at 23:20
• In the < case, rounding is involved. What are the rules for this? For example, what if the original value is .0009 or .04? It can't just be to 2 decimal places else =.04 will become <.04. So what can we assume here and what's the target behaviour? – cfr Mar 27 '15 at 23:37

Here's an implementation of all your requirements:

\documentclass{article}

\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\reportANOVA}{O{}m}
{
\group_begin:
\keys_set:nn { vousden/report } { #1 }
\vousden_report:n { #2 }
\group_end:
}

\NewDocumentCommand{\setreportANOVA}{m}
{
\keys_set:nn { vousden/report } { #1 }
}

\keys_define:nn { vousden/report }
{
<      .bool_set:N  = \l_vousden_report_round_bool,
<      .initial:n   = false,
<      .default:n   = true,
func   .bool_set:N  = \l_vousden_report_func_bool,
func   .default:n   = true,
func   .initial:n   = false,
levels .clist_set:N = \l_vousden_report_levels_clist,
levels .initial:n   = { .05 , .01 , .001 }
}

\seq_new:N \l_vousden_report_args_seq

\cs_new_protected:Npn \vousden_report:n #1
{
\seq_set_split:Nnn \l_vousden_report_args_seq { , } { #1 }
\vousden_report_format:
}

\cs_new_protected:Npn \vousden_report_format:
{
$F \bool_if:NTF \l_vousden_report_func_bool { ( \__vousden_report_item:n { 1 }, \__vousden_report_item:n { 2 } ) } { \c_math_subscript_token { \__vousden_report_item:n { 1 }, \__vousden_report_item:n { 2 } } } = \__vousden_report_item:n { 3 }$,~
$p \bool_if:NTF \l_vousden_report_round_bool { \__vousden_report_round:f { \__vousden_report_item:n { 4 } } } { = \__vousden_report_item:n { 4 } }$
}

\cs_new:Npn \__vousden_report_item:n #1
{
\seq_item:Nn \l_vousden_report_args_seq { #1 }
}

\cs_new_protected:Npn \__vousden_report_round:n #1
{
\clist_map_inline:Nn \l_vousden_report_levels_clist
{
\fp_compare:nT { #1 <= ##1 } { \tl_set:Nn \l_tmpa_tl { ##1 } }
}
\fp_compare:nTF { #1 == \l_tmpa_tl } { = } { < } \l_tmpa_tl
}

\cs_generate_variant:Nn \__vousden_report_round:n { f }

\ExplSyntaxOff

\begin{document}

Stats are \reportANOVA{1,16,5.06,.009}

Stats are \reportANOVA[func]{1,16,5.06,.009}

Stats are \reportANOVA[<]{1,16,5.06,.009}

Stats are \reportANOVA[func,<]{1,16,5.06,.001}

\setreportANOVA{func,<}

Stats are \reportANOVA{1,16,5.06,.009}

\end{document}


The keys that can be specified are

• < for choosing the “levels” format for p
• func for choosing the “function” format
• levels for stating the levels (default .05,.01,.001); if specified, the levels should be expressed in decreasing order

You can use \setreportANOVA for deciding the format from that point on (respecting grouping), or specify the keys in the optional argument.

Note that, if the value for p equals one of the chosen levels, = is used instead of < even if the < key has been specified.

• I'm always impressed with the power of the expl-code but I always cringe at the look and feel of the syntax. Mind you, the "traditional" tex syntax isn't either but it would be nice if we could improve both the power and the syntax at once... (These comments in no way detract from egreg's answer!) – Andrew Mar 28 '15 at 3:39
\newcommand*\reportANOVA[1]{\reportANOVAaux#1\relax}
\def\reportANOVAaux#1,#2,#3,#4\relax{\ensuremath{F_{#1,#2}=#3},\ \ensuremath{p=#4}}


The reason for \ensuremath is because I don't know where do you want to use it, wether

in text mode \reportANOVA{1,2,3,4} or in math mode
$\reportANOVA{5,6,7,8}$


Edited to support the < version. The \reportANOVA command works in a “default way”, and with the star \reportANOVA*{..} you trigger some comparison of numbers: if the fourth argument is smaller than 0.001 then p < 0.001 is printed; if the fourth argument is smaller than 0.01 then p < 0.01 is printed; if it's bigger then we print p < 0.05 (no check here, so we suppose it's always smaller than that one). You may want to look at siunitx to format the numbers, but in this small case it seems not necessary.

\documentclass{scrartcl}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand \reportANOVA { s m }
{
\IfBooleanTF{#1}
{ \vousden_report_anova_alt:wwww #2 \q_stop }
{ \vousden_report_anova:wwww #2 \q_stop }
}
\cs_new_protected:Npn \vousden_report_anova:wwww #1 , #2 , #3 , #4 \q_stop
{ \ensuremath { F \sb{ #1 , #2 } = #3 }, \ \ensuremath { p = #4 } }
\cs_new_protected:Npn \vousden_report_anova_alt:wwww #1 , #2 , #3 , #4 \q_stop
{
\group_begin:
\fp_compare:nNnTF { #4 } < { 0.001 }
{ \fp_set:Nn \l_tmpa_fp { 0.001 } }
{
\fp_compare:nNnTF { #4 } < { 0.01 }
{ \fp_set:Nn \l_tmpa_fp { 0.01 } }
{ \fp_set:Nn \l_tmpa_fp { 0.05 } }
}
\ensuremath { F \sb{ #1 , #2 } = #3 }, \ \ensuremath { p < \fp_use:N \l_tmpa_fp }
\group_end:
}
\ExplSyntaxOff

\begin{document}
Foo \reportANOVA{1,16,5.06,.009} bar \reportANOVA*{1,16,5.06,.009} baz
$\reportANOVA*{1,16,5.06,.0007} \quad \textrm{and} \quad \reportANOVA*{1,16,5.06,.04}$
\end{document}


# EDIT (Revised Answer for Revised Question)

This uses xparse to define the command and the TiKZ maths library to handle the comparison of values.

• \reportANOVA{} takes 1 mandatory argument.
• When used this way, any optional argument is discarded.
• In this case, the mandatory argument should consist of 4 values separated by commas, corresponding to the requested syntax in the question.
• For example \reportANOVA{1,3,4,56.9943}.
• The fourth is the value of p. (So should never be 56.9943!)
• \reportANOVA*[]{} takes 1 mandatory and 1 optional argument.
• This starred form tests the value of p against 3 values, as requested.
• By default, these are 0.001, 0.01 and 0.05.
• If the optional argument is specified, it should provide 3 values in a comma separated list.
• So \reportANOVA*[0.001,0.01,0.05]{10,4.5,6,0.0345} is equivalent to \reportANOVA*{10,4.5,6,0.0345}.
• The values passed to the optional argument are tried in turn, so the smallest should be given first.
• That is, \reportANOVA*[0.001,0.01,0.05]{10,4.5,6,0.0045} will NOT produce the same result as \reportANOVA*[0.05,0.001,0.01]{10,4.5,6,0.0045}.
• If p exceeds all three tested values, the standard, non-starred format will be used instead.
• Otherwise, p will be specified as less than the first of the three values for which this claim is true.

Code:

\documentclass{article}
\usepackage{xparse,tikz}
\usetikzlibrary{math}
\makeatletter
\NewDocumentCommand\reportANOVA{ s > { \SplitArgument { 2 } { , } } O{.001,.01,.05} > { \SplitArgument { 3 } { , } } m }{%
\IfBooleanTF {#1}{%
\@reportANOVA* #2#3}{%
\@reportANOVA #2#3}}
\NewDocumentCommand\@reportANOVA { s G{.001} G{.01} G{.05} m m m m }{%
\IfBooleanTF {#1}{%
\tikzmath{
if #8 < #2 then { print $F_{#5,#6} = #7, P<#2$; } else {%
if #8 < #3 then { print $F_{#5,#6} = #7, P<#3$; } else {%
if #8 < #4 then { print $F_{#5,#6} = #7, P<#4$; }
else {%
print $F_{#5,#6} = #7, P=#8$;
};
};
};
}
}{%
$F_{#5,#6} = #7, P=#8$}}
\makeatother
\begin{document}

\reportANOVA{1,16,5.06,.009}

\reportANOVA*{1,16,5.06,.009}

\reportANOVA*{1,16,5.06,.04}

\reportANOVA*[.025,.05,.075]{1,16,5.06,.01}

\reportANOVA*[.025,.05,.075]{1,16,5.06,.05}

\end{document}


Here's an xparse solution. I've treated the function as maths but obviously, you can specify the format as text if preferred.

\documentclass{article}
\usepackage{xparse}
\makeatletter
\NewDocumentCommand\reportANOVA{ > { \SplitArgument { 3 } { , } } m }{%
\@reportANOVA #1}
\NewDocumentCommand\@reportANOVA { m m m m }{%
$F_{#1,#2} = #3, P=#4$}
\makeatother
\begin{document}
\reportANOVA{1,16,5.06,.009}
\end{document}


• In a leading optional argument you can specify = or < for p – egreg Mar 27 '15 at 23:23
• @egreg Yes. But how to do the rounding up in a non-arbitrary way for the < case? In this case, the intended output is then < .01. But if the original figure was .0009 should it then be .001? Or if it was .04 should it be < .05? And how can that be figured out automatically? – cfr Mar 27 '15 at 23:27

It is a pity do not use knitr if you know R for things like that ...

This file with a .Rnw extension can be compiled out of the box with rstudio:

\documentclass{article}
\usepackage{fixltx2e}
\parindent 0pt

\begin{document}

Making some data ...

<<some data,echo=F,results='asis'>>=
library(xtable)
library(pander)
factor = c("A","A","A","B","B","B","C","C","C")
variable = c(7,4,8,20,17,16,11,12,9)
Data <- data.frame(factor,variable)
xtable(Data,caption="Data used in this example.")
@

(wait a moment, I am thinking ...) \bigskip

<<anova,echo=F,results='hide'>>=
myaov <- aov(variable~factor,data=Data)
res <- summary(myaov)
sig <- round(as.numeric(res[[1]]$Pr[1]),3) df1 <- res[[1]]$Df[1]
df2 <- res[[1]]$Df[2] F <- round(res[[1]]$F[1],2)
PRINT <- paste ("F\\textsubscript{(",df1,",",df2,")}=",F,", p=",p,sep="")
@

Well, I think that the and the result is \Sexpr{PRINT}.

OK ok, you do not believe me, so here is the full result:

<<table,echo=FALSE,results='asis'>>=
xtable(myaov,caption="One way ANOVA used in this example.")
@

Now I will try again with other data frame  ...

<<another,echo=FALSE,results='asis'>>=
rm(PRINT)
factor = c("A","A","A","B","B","B","C","C","C","D","D","D")
variable = c(7,4,8,20,17,16,11,12,9,24,74,11)
Data <- data.frame(factor,variable)
xtable(Data,caption="Another sample.")
<<anova>>
@

Now the result is \Sexpr{PRINT}. You can safely believe that I do not make any misktake passing the results from the statistical program to here. \verb|;)|

\end{document}

• I originally started to learn LaTeX so I could use knitr, so it's interesting you mention this. I can't decide whether it's worth the extra effort to have reproducibility within my results section. I certainly like the idea. I assume it's possible to change the font of the \Sexpr output to match the rest of the text? – George Vousden Mar 28 '15 at 14:38
• @GeorgeVousden "Of course" to the last question. Simply remove {\\sffamily ...} stuff in the R chunk that pass as {\sffamily ....} to the LaTeX ouput. With respect the first, at least for me is a must when I have a lot of tests to make in the same way, or the data may be updated or you neeed show a reproducible research. There are no comparison with conventional statistical software and the copy & paste again and again. – Fran Mar 28 '15 at 15:42
• What/how would we need to escape the PRINT string if we want to pass it directly to \Sexpr? (e.g. \Sexpr{"foo\\textsubscript{bar}"} fails) – jbaums Feb 27 '16 at 2:04
• @jbaums The problem in your example is not the backslashes transcendancy in R to LaTeX conversion, but that you do not pass to R "foo\\textsubscript{bar}", only "foo\\textsubscript{bar (without }") because the first } ended the S (R) expression. Try that directly in R. Even if R could read this without error, the remaining }" would be indigestible for LaTeX. However, note that \Sexpr{"foo\\textsubscript{bar"}} will work. – Fran Feb 27 '16 at 4:03

You need something like this:

\newcommand\ANOVA[4]{$F_{#1,#2}=#3$, $P=#4$} % for F_{1,16}=5.06, P=0.009}

\newcommand\ANONA[4]{$F(#1,#2)=#3$, $P=#4$}


This is using the recommended latex idoms. You then use these command with

\ANOVA{1}{16}{5.06}{0.009}


A better alternative is to TeX \def command:

\def\ANONA(#1,#2,#3,#4){$F_{#1,#2}=#3$, $P=#4$}


Now you simply type \ANONA(1,16,50.06,0.009).

There is one caveats, however, depending on how you intend to use these. I have written these macros assuming that you want to use these in text. If you want to use them in mathematics then delete all of the dollar signs above. You can also define the macros using \ensuremath but my personal preference is to always avoid and write the macro according to how I intend to use it.

• (+1) but this only lets you have your cake or eat it - xparse lets me have my cake and eat it! – cfr Mar 27 '15 at 23:31

Set up to be used in text mode. Allows for auto-switching of default format.

EDITED to provide an optional argument to override default format.

REDITED to achieve roundup, using Werner's approach from How to round number to two digits after the decimal place and have a comma separator?

\documentclass{article}
\usepackage{siunitx}
\ExplSyntaxOn
\cs_new_eq:NN \calc \fp_eval:n
\ExplSyntaxOff

\newcommand\AreportANOVA[4]{$F_{#1,#2}=#3,P=#4$}
\newcommand\BreportANOVA[4]{$F(#1,#2)=#3,P=#4$}
\newcommand\CreportANOVA[4]{$F(#1,#2)=#3,P<\formatNumber{.005+#4}$}
\newcommand\reportformat[1]{%
\if A#1\let\xreportANOVA\AreportANOVA\else
\if B#1\let\xreportANOVA\BreportANOVA\else
\if C#1\let\xreportANOVA\CreportANOVA\fi\fi\fi
}
\reportformat{A}% THE DEFAULT
\newcommand\reportANOVA[5][X]{\reportformat{#1}\xreportANOVA{#2}{#3}{#4}{#5}}
\newcommand*{\formatNumber}[2][]{\num[%
round-mode=places,% Round output to specified number of places
round-precision=2,% Round-precision is 3
output-decimal-marker={.},% Use , as decimal marker
#1% Other options
]{\calc{#2}}}
\begin{document}
A-format currently default.\par
\reportANOVA{1}{16}{5.06}{.009}\par
But others can be invoked with optional argument\par
\reportANOVA[B]{1}{16}{5.06}{.009}\par
\reportANOVA[C]{1}{16}{5.06}{.009}\par
\reportformat{C}
Now C-format is the default\par
\reportANOVA{1}{16}{5.06}{.042}\par
\end{document}


• But for the < case, it is supposed to give < .01... – cfr Mar 27 '15 at 23:29
• @cfr I hadn't noted that in the original question. That takes it to a new level. I'll think on it. – Steven B. Segletes Mar 27 '15 at 23:32
• Indeed. That's why my answer doesn't include the < option ;). However, I'm not sure what the rules are here. What if the initial value is .0009 or .04? I'm unclear on what the target functionality is! – cfr Mar 27 '15 at 23:34
• @cfr We don't know, and, unless the OP tells, we won't know. It's possible to use \pgfmathparse or \fp_eval:n to round the fourth argument to a desired precission, so I think we don't have to worry. @Steven, altough this is nice, he explicitly said that he would change the output for the whole document, so just redefining \reportANOVA would be enough, no need for “middle document styles”. – Manuel Mar 27 '15 at 23:36
• @cfr I just learned again why never to "hand edit" an answer...a stuttering finger inserted an extra }. I have repaired the code (with a full copy/paste) including your example of .042. – Steven B. Segletes Mar 28 '15 at 11:30