format-multiple-errors 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- format_multiple_errors-0.0.2/LICENSE +21 -0
- format_multiple_errors-0.0.2/PKG-INFO +188 -0
- format_multiple_errors-0.0.2/README.md +174 -0
- format_multiple_errors-0.0.2/format_multiple_errors/__init__.py +17 -0
- format_multiple_errors-0.0.2/format_multiple_errors/__main__.py +186 -0
- format_multiple_errors-0.0.2/format_multiple_errors/formatter.py +342 -0
- format_multiple_errors-0.0.2/format_multiple_errors/pandas.py +227 -0
- format_multiple_errors-0.0.2/format_multiple_errors/typing.py +16 -0
- format_multiple_errors-0.0.2/format_multiple_errors.egg-info/PKG-INFO +188 -0
- format_multiple_errors-0.0.2/format_multiple_errors.egg-info/SOURCES.txt +16 -0
- format_multiple_errors-0.0.2/format_multiple_errors.egg-info/dependency_links.txt +1 -0
- format_multiple_errors-0.0.2/format_multiple_errors.egg-info/entry_points.txt +2 -0
- format_multiple_errors-0.0.2/format_multiple_errors.egg-info/top_level.txt +1 -0
- format_multiple_errors-0.0.2/pyproject.toml +28 -0
- format_multiple_errors-0.0.2/setup.cfg +4 -0
- format_multiple_errors-0.0.2/tests/test_cli.py +100 -0
- format_multiple_errors-0.0.2/tests/test_format.py +412 -0
- format_multiple_errors-0.0.2/tests/test_pandas.py +138 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Ed Bennett/Swansea University
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: format_multiple_errors
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: A small widget to be able to format multiple, asymmetric errors easily.
|
|
5
|
+
Author-email: Ed Bennett <e.j.bennett@swansea.ac.uk>
|
|
6
|
+
Project-URL: Homepage, https://github.com/edbennett/format_multiple_errors
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/edbennett/format_multiple_errors/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
|
|
15
|
+
# format_multiple_errors
|
|
16
|
+
|
|
17
|
+
[](https://github.com/edbennett/format_multiple_errors/actions/workflows/pytest.yaml)
|
|
18
|
+
[](https://github.com/edbennett/format_multiple_errors/actions/workflows/codequality.yaml)
|
|
19
|
+
|
|
20
|
+
A small library intended to make it easy to format numbers like
|
|
21
|
+
|
|
22
|
+
$$1.623(11)({}^{3}_{4})\times 10^{-7}$$
|
|
23
|
+
|
|
24
|
+
or
|
|
25
|
+
|
|
26
|
+
$$(6.829 \pm 0.013 {}^{+0.104}_{-0.096})\times10^{5}$$
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
To install, open a terminal and run:
|
|
32
|
+
|
|
33
|
+
pip install https://github.com/edbennett/format_multiple_errors
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## Usage as a library
|
|
37
|
+
|
|
38
|
+
### Formatting numbers
|
|
39
|
+
|
|
40
|
+
The `format_multiple_errors` package provides a function to format numbers,
|
|
41
|
+
also named `format_multiple_errors`.
|
|
42
|
+
|
|
43
|
+
from format_multiple_errors import format_multiple_errors
|
|
44
|
+
|
|
45
|
+
This function takes a central value,
|
|
46
|
+
and zero or more uncertainties.
|
|
47
|
+
It returns a string containing all numbers formatted to the same absolute precision.
|
|
48
|
+
|
|
49
|
+
Uncertainties may be single numbers:
|
|
50
|
+
|
|
51
|
+
>>> format_multiple_errors(1, 0.1, 0.2)
|
|
52
|
+
'1.00 ± 0.10 ± 0.20'
|
|
53
|
+
|
|
54
|
+
or they may also be tuples of two numbers,
|
|
55
|
+
representing the upper and lower error respectively:
|
|
56
|
+
|
|
57
|
+
>>> format_multiple_errors(1, 0.1, (0.2, 0.3))
|
|
58
|
+
'1.00 ± 0.10 (+0.20 / -0.30)'
|
|
59
|
+
|
|
60
|
+
A number of keyword arguments control the formatting.
|
|
61
|
+
The `abbreviate` option uses the compact form of expressing errors:
|
|
62
|
+
|
|
63
|
+
>>> format_multiple_errors(1.001, 0.010, (0.020, 0.034), abbreviate=True)
|
|
64
|
+
'1.001(10)(+20 / -34)'
|
|
65
|
+
|
|
66
|
+
The `latex` option uses LaTeX macros rather than Unicode characters,
|
|
67
|
+
and generally formats the result for inclusion in a LaTeX document:
|
|
68
|
+
|
|
69
|
+
>>> format_multiple_errors(1.001, 0.010, (0.020, 0.034), latex=True)
|
|
70
|
+
'1.001 \\pm 0.010 {}^{+0.020}_{-0.034}'
|
|
71
|
+
|
|
72
|
+
Numbers are formatted by default with leading or trailing zeroes.
|
|
73
|
+
To use exponential notation instead,
|
|
74
|
+
use the `exponential` parameter:
|
|
75
|
+
|
|
76
|
+
>>> format_multiple_errors(0.00123, 0.00045, (0.00067, 0.00089), exponential=True)
|
|
77
|
+
'(1.23 ± 0.45 (+0.67 / -0.89))e-3'
|
|
78
|
+
|
|
79
|
+
By default the precision is controlled
|
|
80
|
+
by setting the number of significant digits presented for the smallest uncertainty.
|
|
81
|
+
Setting `length_control="central"` instead controls the significant digits of the central value:
|
|
82
|
+
|
|
83
|
+
>>> format_multiple_errors(1.0, 0.1, (0.2, 0.3), length_control="central")
|
|
84
|
+
'1.0 ± 0.1 (+0.2 / -0.3)'
|
|
85
|
+
|
|
86
|
+
The number of significant figures presented can be controlled with the `significant_figures` parameter:
|
|
87
|
+
|
|
88
|
+
>>> format_multiple_errors(1.001, 0.001, (0.002, 0.0034), significant_figures=1)
|
|
89
|
+
'1.001 ± 0.001 (+0.002 / -0.003)'
|
|
90
|
+
|
|
91
|
+
These options may be combined:
|
|
92
|
+
|
|
93
|
+
>>> format_multiple_errors(123.45, 3.14, (2.82, 12.91), length_control="central", significant_figures=5, latex=True, abbreviate=True, exponential=True)
|
|
94
|
+
'1.2345(314)({}^{282}_{1291}) \\times 10^{2}'
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
### Formatting DataFrames
|
|
98
|
+
|
|
99
|
+
The library provides two functions for working with Pandas DataFrames.
|
|
100
|
+
|
|
101
|
+
The `format_column_errors` function accepts and returns columns.
|
|
102
|
+
For example,
|
|
103
|
+
it can take `pd.Series` objects:
|
|
104
|
+
|
|
105
|
+
>>> import pandas as pd
|
|
106
|
+
>>> from format_multiple_errors import format_column_errors
|
|
107
|
+
>>> df = pd.DataFrame([{"a": 3.14, "b": 0.59, "c": 0.26}, {"a": 2.17, "b": 0.82, "c": 0.81}])
|
|
108
|
+
>>> format_column_errors(df["a"], (df["b"], df["c"]), abbreviate=True)
|
|
109
|
+
0 3.14(+59/-26)
|
|
110
|
+
1 2.17(+82/-81)
|
|
111
|
+
dtype: object
|
|
112
|
+
|
|
113
|
+
It can also take a `pd.DataFrame` and specifications of the column names to use:
|
|
114
|
+
|
|
115
|
+
>>> format_column_errors("a", ("b", "c"), df=df, abbreviate=True)
|
|
116
|
+
0 3.14(+59/-26)
|
|
117
|
+
1 2.17(+82/-81)
|
|
118
|
+
dtype: object
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
### Interaction with `pyerrors` and `uncertainties`
|
|
122
|
+
|
|
123
|
+
If the central value passed to `format_multiple_errors` already has an uncertainty,
|
|
124
|
+
due to being an instance of `pyerrors.Obs` or `uncertainties.UFloat`,
|
|
125
|
+
then this is prepended to the list of errors.
|
|
126
|
+
For example,
|
|
127
|
+
|
|
128
|
+
>>> from uncertainties import ufloat
|
|
129
|
+
>>> result = ufloat(1.01, 0.1)
|
|
130
|
+
>>> systematic = (0.2, 0.34)
|
|
131
|
+
>>> format_multiple_errors(result, systematic)
|
|
132
|
+
'1.01 ± 0.10 (+0.20 / -0.34)'
|
|
133
|
+
|
|
134
|
+
Instances of `pyerrors.Obs` must already have an uncertainty computed
|
|
135
|
+
(must have had the `.gamma_method()` method called on them)
|
|
136
|
+
before being passed to `format_multiple_errors`,
|
|
137
|
+
otherwise an error is raised.
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
## Command-line interface
|
|
141
|
+
|
|
142
|
+
A command-line interface is also provided.
|
|
143
|
+
To format a single number,
|
|
144
|
+
use the `format_multiple_errors format` command:
|
|
145
|
+
|
|
146
|
+
$ format_multiple_errors --abbreviate format 3.141 0.059 0.026,0.053
|
|
147
|
+
3.141(59)(+26/-53)
|
|
148
|
+
|
|
149
|
+
To format a CSV as a LaTeX table,
|
|
150
|
+
use the `format_multiple_errors table` command:
|
|
151
|
+
|
|
152
|
+
$ format_multiple_errors --latex --abbreviate table input.csv \
|
|
153
|
+
> a b c_value,c_error d_value,d_upper-d_lower,d_systematic \
|
|
154
|
+
> --headings '$a$' '$b$' '$c$' '$d$' --output_file output.tex
|
|
155
|
+
$ cat output.tex
|
|
156
|
+
\begin{tabular}{rrll}
|
|
157
|
+
\toprule
|
|
158
|
+
$a$ & $b$ & $c$ & $d$ \\
|
|
159
|
+
\midrule
|
|
160
|
+
3 & 1 & $4.16(26)$ & $3.59({}^{79}_{24})(46)$ \\
|
|
161
|
+
2 & 7 & $1.83(18)$ & $8.459({}^{45}_{235})(360)$ \\
|
|
162
|
+
\bottomrule
|
|
163
|
+
\end{tabular}
|
|
164
|
+
|
|
165
|
+
Formatting options are specified before the subcommand (`format` or `table`).
|
|
166
|
+
Options specific to the command are specified afterwards.
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
To be able to run the test suite,
|
|
172
|
+
create a virtual environment using the tooling of your choice,
|
|
173
|
+
and then install the developer dependencies:
|
|
174
|
+
|
|
175
|
+
pip install -r requirements_dev.txt
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
The test suite can then be run by calling
|
|
179
|
+
|
|
180
|
+
pytest
|
|
181
|
+
|
|
182
|
+
Before committing,
|
|
183
|
+
you should ensure that the repository's pre-commit hooks are installed:
|
|
184
|
+
|
|
185
|
+
pre-commit install
|
|
186
|
+
|
|
187
|
+
Then some basic code quality checks will be run by Git
|
|
188
|
+
before it accepts your commit.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# format_multiple_errors
|
|
2
|
+
|
|
3
|
+
[](https://github.com/edbennett/format_multiple_errors/actions/workflows/pytest.yaml)
|
|
4
|
+
[](https://github.com/edbennett/format_multiple_errors/actions/workflows/codequality.yaml)
|
|
5
|
+
|
|
6
|
+
A small library intended to make it easy to format numbers like
|
|
7
|
+
|
|
8
|
+
$$1.623(11)({}^{3}_{4})\times 10^{-7}$$
|
|
9
|
+
|
|
10
|
+
or
|
|
11
|
+
|
|
12
|
+
$$(6.829 \pm 0.013 {}^{+0.104}_{-0.096})\times10^{5}$$
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
To install, open a terminal and run:
|
|
18
|
+
|
|
19
|
+
pip install https://github.com/edbennett/format_multiple_errors
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## Usage as a library
|
|
23
|
+
|
|
24
|
+
### Formatting numbers
|
|
25
|
+
|
|
26
|
+
The `format_multiple_errors` package provides a function to format numbers,
|
|
27
|
+
also named `format_multiple_errors`.
|
|
28
|
+
|
|
29
|
+
from format_multiple_errors import format_multiple_errors
|
|
30
|
+
|
|
31
|
+
This function takes a central value,
|
|
32
|
+
and zero or more uncertainties.
|
|
33
|
+
It returns a string containing all numbers formatted to the same absolute precision.
|
|
34
|
+
|
|
35
|
+
Uncertainties may be single numbers:
|
|
36
|
+
|
|
37
|
+
>>> format_multiple_errors(1, 0.1, 0.2)
|
|
38
|
+
'1.00 ± 0.10 ± 0.20'
|
|
39
|
+
|
|
40
|
+
or they may also be tuples of two numbers,
|
|
41
|
+
representing the upper and lower error respectively:
|
|
42
|
+
|
|
43
|
+
>>> format_multiple_errors(1, 0.1, (0.2, 0.3))
|
|
44
|
+
'1.00 ± 0.10 (+0.20 / -0.30)'
|
|
45
|
+
|
|
46
|
+
A number of keyword arguments control the formatting.
|
|
47
|
+
The `abbreviate` option uses the compact form of expressing errors:
|
|
48
|
+
|
|
49
|
+
>>> format_multiple_errors(1.001, 0.010, (0.020, 0.034), abbreviate=True)
|
|
50
|
+
'1.001(10)(+20 / -34)'
|
|
51
|
+
|
|
52
|
+
The `latex` option uses LaTeX macros rather than Unicode characters,
|
|
53
|
+
and generally formats the result for inclusion in a LaTeX document:
|
|
54
|
+
|
|
55
|
+
>>> format_multiple_errors(1.001, 0.010, (0.020, 0.034), latex=True)
|
|
56
|
+
'1.001 \\pm 0.010 {}^{+0.020}_{-0.034}'
|
|
57
|
+
|
|
58
|
+
Numbers are formatted by default with leading or trailing zeroes.
|
|
59
|
+
To use exponential notation instead,
|
|
60
|
+
use the `exponential` parameter:
|
|
61
|
+
|
|
62
|
+
>>> format_multiple_errors(0.00123, 0.00045, (0.00067, 0.00089), exponential=True)
|
|
63
|
+
'(1.23 ± 0.45 (+0.67 / -0.89))e-3'
|
|
64
|
+
|
|
65
|
+
By default the precision is controlled
|
|
66
|
+
by setting the number of significant digits presented for the smallest uncertainty.
|
|
67
|
+
Setting `length_control="central"` instead controls the significant digits of the central value:
|
|
68
|
+
|
|
69
|
+
>>> format_multiple_errors(1.0, 0.1, (0.2, 0.3), length_control="central")
|
|
70
|
+
'1.0 ± 0.1 (+0.2 / -0.3)'
|
|
71
|
+
|
|
72
|
+
The number of significant figures presented can be controlled with the `significant_figures` parameter:
|
|
73
|
+
|
|
74
|
+
>>> format_multiple_errors(1.001, 0.001, (0.002, 0.0034), significant_figures=1)
|
|
75
|
+
'1.001 ± 0.001 (+0.002 / -0.003)'
|
|
76
|
+
|
|
77
|
+
These options may be combined:
|
|
78
|
+
|
|
79
|
+
>>> format_multiple_errors(123.45, 3.14, (2.82, 12.91), length_control="central", significant_figures=5, latex=True, abbreviate=True, exponential=True)
|
|
80
|
+
'1.2345(314)({}^{282}_{1291}) \\times 10^{2}'
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
### Formatting DataFrames
|
|
84
|
+
|
|
85
|
+
The library provides two functions for working with Pandas DataFrames.
|
|
86
|
+
|
|
87
|
+
The `format_column_errors` function accepts and returns columns.
|
|
88
|
+
For example,
|
|
89
|
+
it can take `pd.Series` objects:
|
|
90
|
+
|
|
91
|
+
>>> import pandas as pd
|
|
92
|
+
>>> from format_multiple_errors import format_column_errors
|
|
93
|
+
>>> df = pd.DataFrame([{"a": 3.14, "b": 0.59, "c": 0.26}, {"a": 2.17, "b": 0.82, "c": 0.81}])
|
|
94
|
+
>>> format_column_errors(df["a"], (df["b"], df["c"]), abbreviate=True)
|
|
95
|
+
0 3.14(+59/-26)
|
|
96
|
+
1 2.17(+82/-81)
|
|
97
|
+
dtype: object
|
|
98
|
+
|
|
99
|
+
It can also take a `pd.DataFrame` and specifications of the column names to use:
|
|
100
|
+
|
|
101
|
+
>>> format_column_errors("a", ("b", "c"), df=df, abbreviate=True)
|
|
102
|
+
0 3.14(+59/-26)
|
|
103
|
+
1 2.17(+82/-81)
|
|
104
|
+
dtype: object
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
### Interaction with `pyerrors` and `uncertainties`
|
|
108
|
+
|
|
109
|
+
If the central value passed to `format_multiple_errors` already has an uncertainty,
|
|
110
|
+
due to being an instance of `pyerrors.Obs` or `uncertainties.UFloat`,
|
|
111
|
+
then this is prepended to the list of errors.
|
|
112
|
+
For example,
|
|
113
|
+
|
|
114
|
+
>>> from uncertainties import ufloat
|
|
115
|
+
>>> result = ufloat(1.01, 0.1)
|
|
116
|
+
>>> systematic = (0.2, 0.34)
|
|
117
|
+
>>> format_multiple_errors(result, systematic)
|
|
118
|
+
'1.01 ± 0.10 (+0.20 / -0.34)'
|
|
119
|
+
|
|
120
|
+
Instances of `pyerrors.Obs` must already have an uncertainty computed
|
|
121
|
+
(must have had the `.gamma_method()` method called on them)
|
|
122
|
+
before being passed to `format_multiple_errors`,
|
|
123
|
+
otherwise an error is raised.
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
## Command-line interface
|
|
127
|
+
|
|
128
|
+
A command-line interface is also provided.
|
|
129
|
+
To format a single number,
|
|
130
|
+
use the `format_multiple_errors format` command:
|
|
131
|
+
|
|
132
|
+
$ format_multiple_errors --abbreviate format 3.141 0.059 0.026,0.053
|
|
133
|
+
3.141(59)(+26/-53)
|
|
134
|
+
|
|
135
|
+
To format a CSV as a LaTeX table,
|
|
136
|
+
use the `format_multiple_errors table` command:
|
|
137
|
+
|
|
138
|
+
$ format_multiple_errors --latex --abbreviate table input.csv \
|
|
139
|
+
> a b c_value,c_error d_value,d_upper-d_lower,d_systematic \
|
|
140
|
+
> --headings '$a$' '$b$' '$c$' '$d$' --output_file output.tex
|
|
141
|
+
$ cat output.tex
|
|
142
|
+
\begin{tabular}{rrll}
|
|
143
|
+
\toprule
|
|
144
|
+
$a$ & $b$ & $c$ & $d$ \\
|
|
145
|
+
\midrule
|
|
146
|
+
3 & 1 & $4.16(26)$ & $3.59({}^{79}_{24})(46)$ \\
|
|
147
|
+
2 & 7 & $1.83(18)$ & $8.459({}^{45}_{235})(360)$ \\
|
|
148
|
+
\bottomrule
|
|
149
|
+
\end{tabular}
|
|
150
|
+
|
|
151
|
+
Formatting options are specified before the subcommand (`format` or `table`).
|
|
152
|
+
Options specific to the command are specified afterwards.
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
## Development
|
|
156
|
+
|
|
157
|
+
To be able to run the test suite,
|
|
158
|
+
create a virtual environment using the tooling of your choice,
|
|
159
|
+
and then install the developer dependencies:
|
|
160
|
+
|
|
161
|
+
pip install -r requirements_dev.txt
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
The test suite can then be run by calling
|
|
165
|
+
|
|
166
|
+
pytest
|
|
167
|
+
|
|
168
|
+
Before committing,
|
|
169
|
+
you should ensure that the repository's pre-commit hooks are installed:
|
|
170
|
+
|
|
171
|
+
pre-commit install
|
|
172
|
+
|
|
173
|
+
Then some basic code quality checks will be run by Git
|
|
174
|
+
before it accepts your commit.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""A package for formatting numbers with zero or more errors or pairs of errors"""
|
|
4
|
+
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
7
|
+
from .formatter import format_multiple_errors as format_multiple_errors
|
|
8
|
+
|
|
9
|
+
with warnings.catch_warnings():
|
|
10
|
+
warnings.simplefilter("ignore")
|
|
11
|
+
from .pandas import (
|
|
12
|
+
format_column_errors as format_column_errors,
|
|
13
|
+
format_dataframe_errors as format_dataframe_errors,
|
|
14
|
+
ColumnSpec as ColumnSpec,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
del warnings
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Command-line interface for format_multiple_errors"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from argparse import ArgumentParser, FileType, Namespace
|
|
8
|
+
import logging
|
|
9
|
+
from sys import exit, stderr
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import pandas as pd
|
|
13
|
+
except ImportError:
|
|
14
|
+
pd = None
|
|
15
|
+
|
|
16
|
+
from .formatter import format_multiple_errors
|
|
17
|
+
from .pandas import format_dataframe_errors, ColumnSpec
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _format_numbers(args: Namespace) -> None:
|
|
21
|
+
"""Format a single number."""
|
|
22
|
+
print(
|
|
23
|
+
format_multiple_errors(
|
|
24
|
+
args.value,
|
|
25
|
+
*args.errors,
|
|
26
|
+
length_control=args.length_control,
|
|
27
|
+
significant_figures=args.significant_figures,
|
|
28
|
+
abbreviate=args.abbreviate,
|
|
29
|
+
exponential=args.exponential,
|
|
30
|
+
latex=args.latex,
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _check_pandas() -> None:
|
|
36
|
+
"""Check if Pandas is available; complain and exit if not."""
|
|
37
|
+
if pd is None:
|
|
38
|
+
print(
|
|
39
|
+
"Pandas is not installed, but is required to process a table.",
|
|
40
|
+
file=stderr,
|
|
41
|
+
)
|
|
42
|
+
exit()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _format_table(args: Namespace) -> None:
|
|
46
|
+
"""Format a table from a CSV."""
|
|
47
|
+
_check_pandas()
|
|
48
|
+
if not args.latex:
|
|
49
|
+
logging.warning(
|
|
50
|
+
"--latex not specified; for LaTeX table output this will be forced on.",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if not args.headings:
|
|
54
|
+
args.headings = None
|
|
55
|
+
else:
|
|
56
|
+
assert len(args.headings) == len(args.column_specs)
|
|
57
|
+
|
|
58
|
+
df = pd.read_csv(args.input_file)
|
|
59
|
+
formatted_df = format_dataframe_errors(
|
|
60
|
+
df=df,
|
|
61
|
+
columns=args.column_specs,
|
|
62
|
+
length_control=args.length_control,
|
|
63
|
+
significant_figures=args.significant_figures,
|
|
64
|
+
abbreviate=args.abbreviate,
|
|
65
|
+
exponential=args.exponential,
|
|
66
|
+
latex=True,
|
|
67
|
+
)
|
|
68
|
+
formatted_df.to_latex(args.output_file, index=False, header=args.headings)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _float_or_pair(arg: str) -> float | tuple[float, float]:
|
|
72
|
+
"""Takes a string containing either a float, or two floats separated by commas,
|
|
73
|
+
and returns them as either a float or a tuple of floats."""
|
|
74
|
+
|
|
75
|
+
split_arg = arg.split(",")
|
|
76
|
+
if len(split_arg) == 1:
|
|
77
|
+
return float(arg)
|
|
78
|
+
elif len(split_arg) == 2:
|
|
79
|
+
return tuple(map(float, split_arg))
|
|
80
|
+
else:
|
|
81
|
+
message = f"Can't parse {arg} as a number or pair of numbers."
|
|
82
|
+
raise ValueError(message)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _parse_columnspec(arg: str) -> str | ColumnSpec:
|
|
86
|
+
"""Take a string containing a specification for columns,
|
|
87
|
+
and return a column name (if a single column is specified)
|
|
88
|
+
or a ColumnSpec (if multiple columns are specified)."""
|
|
89
|
+
|
|
90
|
+
split_arg = arg.split(",")
|
|
91
|
+
if len(split_arg) == 1:
|
|
92
|
+
return arg
|
|
93
|
+
|
|
94
|
+
if not isinstance(split_arg[0], str):
|
|
95
|
+
raise ValueError("First column has to be a single central value.")
|
|
96
|
+
|
|
97
|
+
error_columns = []
|
|
98
|
+
for error_spec in split_arg[1:]:
|
|
99
|
+
split_error = error_spec.split("-")
|
|
100
|
+
if len(split_error) == 1:
|
|
101
|
+
error_columns.append(error_spec)
|
|
102
|
+
else:
|
|
103
|
+
error_columns.append(tuple(split_error))
|
|
104
|
+
|
|
105
|
+
return ColumnSpec(split_arg[0], *error_columns)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def get_args(override_args: list | None = None) -> Namespace:
|
|
109
|
+
"""Parse command line."""
|
|
110
|
+
parser = ArgumentParser(prog="format_multiple_errors")
|
|
111
|
+
parser.add_argument(
|
|
112
|
+
"--abbreviate",
|
|
113
|
+
action="store_true",
|
|
114
|
+
help="Abbreviate the uncertainty - e.g. 1.23(4) instead of 1.23 ± 0.04",
|
|
115
|
+
)
|
|
116
|
+
parser.add_argument(
|
|
117
|
+
"--exponential", action="store_true", help="Use exponential notation"
|
|
118
|
+
)
|
|
119
|
+
parser.add_argument(
|
|
120
|
+
"--latex",
|
|
121
|
+
action="store_true",
|
|
122
|
+
help="Use LaTeX rather than plain text format - e.g. 1.23 \\pm 0.04 instead of 1.23 ± 0.04",
|
|
123
|
+
)
|
|
124
|
+
parser.add_argument(
|
|
125
|
+
"--significant_figures",
|
|
126
|
+
type=int,
|
|
127
|
+
default=2,
|
|
128
|
+
help="Number of significant figures to display (used in conjunction with --length_control)",
|
|
129
|
+
)
|
|
130
|
+
parser.add_argument(
|
|
131
|
+
"--length_control",
|
|
132
|
+
default="smallest",
|
|
133
|
+
choices=["smallest", "central"],
|
|
134
|
+
help="Value to control the significant figures of (`smallest` uncertainty or `central` value)",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
subparsers = parser.add_subparsers(title="commands")
|
|
138
|
+
|
|
139
|
+
format_parser = subparsers.add_parser("format", help="Format a single number")
|
|
140
|
+
format_parser.add_argument("value", type=float, help="The central value")
|
|
141
|
+
format_parser.add_argument(
|
|
142
|
+
"errors",
|
|
143
|
+
type=_float_or_pair,
|
|
144
|
+
metavar="error",
|
|
145
|
+
nargs="+",
|
|
146
|
+
help="Uncertainties in the value. Asymmetric uncertainties are specified as upper,lower.",
|
|
147
|
+
)
|
|
148
|
+
format_parser.set_defaults(func=_format_numbers)
|
|
149
|
+
|
|
150
|
+
table_parser = subparsers.add_parser(
|
|
151
|
+
"table", help="Format a CSV file into a LaTeX table"
|
|
152
|
+
)
|
|
153
|
+
table_parser.add_argument(
|
|
154
|
+
"input_file", type=FileType("r"), default="-", help="The CSV file to read in"
|
|
155
|
+
)
|
|
156
|
+
table_parser.add_argument(
|
|
157
|
+
"--output_file",
|
|
158
|
+
type=FileType("w"),
|
|
159
|
+
default="-",
|
|
160
|
+
help="Where to place the output LaTeX",
|
|
161
|
+
)
|
|
162
|
+
table_parser.add_argument(
|
|
163
|
+
"column_specs",
|
|
164
|
+
type=_parse_columnspec,
|
|
165
|
+
metavar="column_spec",
|
|
166
|
+
nargs="+",
|
|
167
|
+
help="Specifications of columns to include in the table, in the form value_column[,error_column[-lower_error_column]][,error_column[-lower_error_column]][...]",
|
|
168
|
+
)
|
|
169
|
+
table_parser.add_argument(
|
|
170
|
+
"--headings",
|
|
171
|
+
nargs="+",
|
|
172
|
+
metavar="heading",
|
|
173
|
+
help="Column headings to use. By default, these are taken from the CSV. If supplied, the count must match the number of specified columns",
|
|
174
|
+
)
|
|
175
|
+
table_parser.set_defaults(func=_format_table)
|
|
176
|
+
return parser.parse_args(override_args)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def cli(override_args: list | None = None) -> None:
|
|
180
|
+
"""Run the CLI."""
|
|
181
|
+
args = get_args(override_args)
|
|
182
|
+
args.func(args)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if __name__ == "__main__":
|
|
186
|
+
cli()
|