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.
@@ -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
+ [![Run tests](https://github.com/edbennett/format_multiple_errors/actions/workflows/pytest.yaml/badge.svg)](https://github.com/edbennett/format_multiple_errors/actions/workflows/pytest.yaml)
18
+ [![Code quality](https://github.com/edbennett/format_multiple_errors/actions/workflows/codequality.yaml/badge.svg)](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
+ [![Run tests](https://github.com/edbennett/format_multiple_errors/actions/workflows/pytest.yaml/badge.svg)](https://github.com/edbennett/format_multiple_errors/actions/workflows/pytest.yaml)
4
+ [![Code quality](https://github.com/edbennett/format_multiple_errors/actions/workflows/codequality.yaml/badge.svg)](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()