itables 2.5.2.dev0__tar.gz → 2.6.0__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.
- {itables-2.5.2.dev0 → itables-2.6.0}/.gitignore +6 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/PKG-INFO +85 -35
- {itables-2.5.2.dev0 → itables-2.6.0}/README.md +4 -6
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/__init__.py +12 -0
- itables-2.6.0/itables/datatables_format.py +211 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/downsample.py +66 -51
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/html/dt_bundle.css +1 -1
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/itables_for_streamlit/asset-manifest.json +3 -3
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/itables_for_streamlit/index.html +1 -1
- itables-2.5.2.dev0/itables/itables_for_streamlit/static/css/main.66715262.css → itables-2.6.0/itables/itables_for_streamlit/static/css/main.d9f03cb9.css +2 -2
- itables-2.6.0/itables/itables_for_streamlit/static/css/main.d9f03cb9.css.map +1 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/itables_for_streamlit/static/js/main.978ec6e6.js.map +1 -1
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/javascript.py +220 -79
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/options.py +19 -4
- itables-2.6.0/itables/sample_dfs.py +32 -0
- itables-2.5.2.dev0/itables/sample_dfs.py → itables-2.6.0/itables/sample_pandas_dfs.py +4 -53
- itables-2.6.0/itables/sample_polars_dfs.py +230 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/shiny.py +2 -4
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/streamlit.py +1 -2
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/typing.py +79 -5
- itables-2.6.0/itables/version.py +4 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/widget/__init__.py +2 -2
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/widget/static/widget.css +1 -1
- {itables-2.5.2.dev0 → itables-2.6.0}/itables_for_dash/ITable.py +1 -1
- {itables-2.5.2.dev0 → itables-2.6.0}/itables_for_dash/__init__.py +2 -2
- {itables-2.5.2.dev0 → itables-2.6.0}/itables_for_dash/async-ITable.js +1 -1
- {itables-2.5.2.dev0 → itables-2.6.0}/itables_for_dash/async-ITable.js.map +1 -1
- {itables-2.5.2.dev0 → itables-2.6.0}/itables_for_dash/itables_for_dash.min.js +1 -1
- {itables-2.5.2.dev0 → itables-2.6.0}/itables_for_dash/itables_for_dash.min.js.map +1 -1
- {itables-2.5.2.dev0 → itables-2.6.0}/itables_for_dash/properties.py +1 -2
- {itables-2.5.2.dev0 → itables-2.6.0}/pyproject.toml +72 -20
- itables-2.5.2.dev0/itables/datatables_format.py +0 -160
- itables-2.5.2.dev0/itables/itables_for_streamlit/static/css/main.66715262.css.map +0 -1
- itables-2.5.2.dev0/itables/version.py +0 -3
- {itables-2.5.2.dev0 → itables-2.6.0}/LICENSE +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/config.py +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/dash.py +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/html/datatables_template.html +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/html/datatables_template_offline.html +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/html/dt_bundle.js +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/html/init_datatables.html +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/html/init_notebook_offline.html +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/interactive.py +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/itables_for_streamlit/static/js/main.978ec6e6.js +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/itables_for_streamlit/static/js/main.978ec6e6.js.LICENSE.txt +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/logo/loading.svg +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/logo/logo.svg +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/logo/text.svg +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/logo/wide.svg +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/logo/with_text.svg +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/py.typed +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/samples/countries.csv +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/samples/indicators.csv +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/samples/population.csv +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/show_config.py +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/utils.py +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables/widget/static/widget.js +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables_for_dash/_imports_.py +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables_for_dash/async-ITable.js.LICENSE.txt +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables_for_dash/metadata.json +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables_for_dash/package-info.json +0 -0
- {itables-2.5.2.dev0 → itables-2.6.0}/itables_for_dash/py.typed +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: itables
|
|
3
|
-
Version: 2.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 2.6.0
|
|
4
|
+
Summary: Python DataFrames as interactive DataTables
|
|
5
5
|
Project-URL: Homepage, https://mwouts.github.io/itables/
|
|
6
6
|
Project-URL: Documentation, https://mwouts.github.io/itables
|
|
7
7
|
Project-URL: Repository, https://github.com/mwouts/itables.git
|
|
@@ -31,7 +31,7 @@ License: MIT License
|
|
|
31
31
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
32
32
|
SOFTWARE.
|
|
33
33
|
License-File: LICENSE
|
|
34
|
-
Keywords: DataTables,Interactive,Javascript,Pandas,Polars
|
|
34
|
+
Keywords: DataTables,Interactive,Javascript,Narwhals,Pandas,Polars
|
|
35
35
|
Classifier: Development Status :: 5 - Production/Stable
|
|
36
36
|
Classifier: Framework :: Dash
|
|
37
37
|
Classifier: Framework :: Jupyter
|
|
@@ -45,15 +45,18 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
45
45
|
Classifier: Programming Language :: Python :: 3.11
|
|
46
46
|
Classifier: Programming Language :: Python :: 3.12
|
|
47
47
|
Classifier: Programming Language :: Python :: 3.13
|
|
48
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
48
49
|
Classifier: Typing :: Typed
|
|
49
50
|
Requires-Python: >=3.9
|
|
50
|
-
Requires-Dist: ipython
|
|
51
|
-
Requires-Dist: numpy
|
|
52
|
-
Requires-Dist: pandas
|
|
53
51
|
Provides-Extra: all
|
|
54
52
|
Requires-Dist: anywidget; extra == 'all'
|
|
55
53
|
Requires-Dist: dash; extra == 'all'
|
|
54
|
+
Requires-Dist: ipython; extra == 'all'
|
|
55
|
+
Requires-Dist: marimo; extra == 'all'
|
|
56
56
|
Requires-Dist: matplotlib; extra == 'all'
|
|
57
|
+
Requires-Dist: modin[dask]; extra == 'all'
|
|
58
|
+
Requires-Dist: narwhals; extra == 'all'
|
|
59
|
+
Requires-Dist: pandas; extra == 'all'
|
|
57
60
|
Requires-Dist: platformdirs; extra == 'all'
|
|
58
61
|
Requires-Dist: polars; extra == 'all'
|
|
59
62
|
Requires-Dist: pyarrow; extra == 'all'
|
|
@@ -64,6 +67,7 @@ Requires-Dist: streamlit; extra == 'all'
|
|
|
64
67
|
Requires-Dist: tomli; (python_version < '3.11') and extra == 'all'
|
|
65
68
|
Requires-Dist: traitlets; extra == 'all'
|
|
66
69
|
Requires-Dist: typeguard>=4.4.1; extra == 'all'
|
|
70
|
+
Requires-Dist: typing-extensions; extra == 'all'
|
|
67
71
|
Requires-Dist: world-bank-data; extra == 'all'
|
|
68
72
|
Provides-Extra: check-type
|
|
69
73
|
Requires-Dist: typeguard>=4.4.1; extra == 'check-type'
|
|
@@ -72,20 +76,28 @@ Requires-Dist: platformdirs; extra == 'config'
|
|
|
72
76
|
Requires-Dist: tomli; (python_version < '3.11') and extra == 'config'
|
|
73
77
|
Provides-Extra: dash
|
|
74
78
|
Requires-Dist: dash; extra == 'dash'
|
|
79
|
+
Requires-Dist: typing-extensions; extra == 'dash'
|
|
75
80
|
Provides-Extra: dev
|
|
76
81
|
Requires-Dist: anywidget; extra == 'dev'
|
|
77
82
|
Requires-Dist: dash; extra == 'dev'
|
|
78
83
|
Requires-Dist: ipykernel; extra == 'dev'
|
|
84
|
+
Requires-Dist: ipython; extra == 'dev'
|
|
79
85
|
Requires-Dist: jupyterlab; extra == 'dev'
|
|
80
86
|
Requires-Dist: jupytext; extra == 'dev'
|
|
81
87
|
Requires-Dist: marimo; extra == 'dev'
|
|
82
88
|
Requires-Dist: matplotlib; extra == 'dev'
|
|
89
|
+
Requires-Dist: modin[dask]; extra == 'dev'
|
|
90
|
+
Requires-Dist: narwhals; extra == 'dev'
|
|
83
91
|
Requires-Dist: nbconvert; extra == 'dev'
|
|
92
|
+
Requires-Dist: pandas; extra == 'dev'
|
|
93
|
+
Requires-Dist: pandas-stubs; extra == 'dev'
|
|
84
94
|
Requires-Dist: platformdirs; extra == 'dev'
|
|
85
95
|
Requires-Dist: polars; extra == 'dev'
|
|
86
96
|
Requires-Dist: pyarrow; extra == 'dev'
|
|
97
|
+
Requires-Dist: pyright; extra == 'dev'
|
|
87
98
|
Requires-Dist: pytest; extra == 'dev'
|
|
88
99
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
100
|
+
Requires-Dist: pytest-xdist; extra == 'dev'
|
|
89
101
|
Requires-Dist: pytz; extra == 'dev'
|
|
90
102
|
Requires-Dist: requests; extra == 'dev'
|
|
91
103
|
Requires-Dist: shiny; extra == 'dev'
|
|
@@ -94,11 +106,23 @@ Requires-Dist: streamlit; extra == 'dev'
|
|
|
94
106
|
Requires-Dist: tomli; (python_version < '3.11') and extra == 'dev'
|
|
95
107
|
Requires-Dist: traitlets; extra == 'dev'
|
|
96
108
|
Requires-Dist: typeguard>=4.4.1; extra == 'dev'
|
|
109
|
+
Requires-Dist: typing-extensions; extra == 'dev'
|
|
97
110
|
Requires-Dist: watchfiles; extra == 'dev'
|
|
98
111
|
Requires-Dist: world-bank-data; extra == 'dev'
|
|
112
|
+
Provides-Extra: marimo
|
|
113
|
+
Requires-Dist: marimo; extra == 'marimo'
|
|
114
|
+
Provides-Extra: narwhals
|
|
115
|
+
Requires-Dist: narwhals; extra == 'narwhals'
|
|
116
|
+
Provides-Extra: notebook
|
|
117
|
+
Requires-Dist: ipython; extra == 'notebook'
|
|
118
|
+
Provides-Extra: other-dataframes
|
|
119
|
+
Requires-Dist: modin[dask]; extra == 'other-dataframes'
|
|
120
|
+
Requires-Dist: narwhals; extra == 'other-dataframes'
|
|
121
|
+
Requires-Dist: pyarrow; extra == 'other-dataframes'
|
|
122
|
+
Provides-Extra: pandas
|
|
123
|
+
Requires-Dist: pandas; extra == 'pandas'
|
|
99
124
|
Provides-Extra: polars
|
|
100
125
|
Requires-Dist: polars; extra == 'polars'
|
|
101
|
-
Requires-Dist: pyarrow; extra == 'polars'
|
|
102
126
|
Provides-Extra: samples
|
|
103
127
|
Requires-Dist: pytz; extra == 'samples'
|
|
104
128
|
Requires-Dist: world-bank-data; extra == 'samples'
|
|
@@ -109,28 +133,56 @@ Provides-Extra: streamlit
|
|
|
109
133
|
Requires-Dist: streamlit; extra == 'streamlit'
|
|
110
134
|
Provides-Extra: style
|
|
111
135
|
Requires-Dist: matplotlib; extra == 'style'
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
Requires-Dist:
|
|
115
|
-
Requires-Dist:
|
|
116
|
-
Requires-Dist:
|
|
117
|
-
Requires-Dist:
|
|
118
|
-
Requires-Dist:
|
|
119
|
-
Requires-Dist:
|
|
120
|
-
Requires-Dist:
|
|
121
|
-
Requires-Dist:
|
|
122
|
-
Requires-Dist:
|
|
123
|
-
Requires-Dist:
|
|
124
|
-
Requires-Dist:
|
|
125
|
-
Requires-Dist:
|
|
126
|
-
Requires-Dist:
|
|
127
|
-
Requires-Dist:
|
|
128
|
-
Requires-Dist:
|
|
129
|
-
Requires-Dist:
|
|
130
|
-
Requires-Dist:
|
|
131
|
-
Requires-Dist:
|
|
132
|
-
Requires-Dist:
|
|
133
|
-
Requires-Dist:
|
|
136
|
+
Requires-Dist: pandas; extra == 'style'
|
|
137
|
+
Provides-Extra: test-all
|
|
138
|
+
Requires-Dist: anywidget; extra == 'test-all'
|
|
139
|
+
Requires-Dist: dash; extra == 'test-all'
|
|
140
|
+
Requires-Dist: ipykernel; extra == 'test-all'
|
|
141
|
+
Requires-Dist: ipython; extra == 'test-all'
|
|
142
|
+
Requires-Dist: jupytext; extra == 'test-all'
|
|
143
|
+
Requires-Dist: marimo; extra == 'test-all'
|
|
144
|
+
Requires-Dist: matplotlib; extra == 'test-all'
|
|
145
|
+
Requires-Dist: modin[dask]; extra == 'test-all'
|
|
146
|
+
Requires-Dist: narwhals; extra == 'test-all'
|
|
147
|
+
Requires-Dist: nbconvert; extra == 'test-all'
|
|
148
|
+
Requires-Dist: pandas; extra == 'test-all'
|
|
149
|
+
Requires-Dist: platformdirs; extra == 'test-all'
|
|
150
|
+
Requires-Dist: polars; extra == 'test-all'
|
|
151
|
+
Requires-Dist: pyarrow; extra == 'test-all'
|
|
152
|
+
Requires-Dist: pytest; extra == 'test-all'
|
|
153
|
+
Requires-Dist: pytest-cov; extra == 'test-all'
|
|
154
|
+
Requires-Dist: pytest-xdist; extra == 'test-all'
|
|
155
|
+
Requires-Dist: pytz; extra == 'test-all'
|
|
156
|
+
Requires-Dist: requests; extra == 'test-all'
|
|
157
|
+
Requires-Dist: shiny; extra == 'test-all'
|
|
158
|
+
Requires-Dist: shinywidgets; extra == 'test-all'
|
|
159
|
+
Requires-Dist: streamlit; extra == 'test-all'
|
|
160
|
+
Requires-Dist: tomli; (python_version < '3.11') and extra == 'test-all'
|
|
161
|
+
Requires-Dist: traitlets; extra == 'test-all'
|
|
162
|
+
Requires-Dist: typeguard>=4.4.1; extra == 'test-all'
|
|
163
|
+
Requires-Dist: typing-extensions; extra == 'test-all'
|
|
164
|
+
Requires-Dist: world-bank-data; extra == 'test-all'
|
|
165
|
+
Provides-Extra: test-base
|
|
166
|
+
Requires-Dist: pytest; extra == 'test-base'
|
|
167
|
+
Requires-Dist: pytest-cov; extra == 'test-base'
|
|
168
|
+
Requires-Dist: pytest-xdist; extra == 'test-base'
|
|
169
|
+
Provides-Extra: test-dash
|
|
170
|
+
Requires-Dist: dash; extra == 'test-dash'
|
|
171
|
+
Requires-Dist: pytest; extra == 'test-dash'
|
|
172
|
+
Requires-Dist: pytest-cov; extra == 'test-dash'
|
|
173
|
+
Requires-Dist: pytest-xdist; extra == 'test-dash'
|
|
174
|
+
Requires-Dist: typing-extensions; extra == 'test-dash'
|
|
175
|
+
Provides-Extra: test-notebook
|
|
176
|
+
Requires-Dist: anywidget; extra == 'test-notebook'
|
|
177
|
+
Requires-Dist: ipykernel; extra == 'test-notebook'
|
|
178
|
+
Requires-Dist: ipython; extra == 'test-notebook'
|
|
179
|
+
Requires-Dist: jupytext; extra == 'test-notebook'
|
|
180
|
+
Requires-Dist: nbconvert; extra == 'test-notebook'
|
|
181
|
+
Requires-Dist: pytest; extra == 'test-notebook'
|
|
182
|
+
Requires-Dist: pytest-cov; extra == 'test-notebook'
|
|
183
|
+
Requires-Dist: pytest-xdist; extra == 'test-notebook'
|
|
184
|
+
Requires-Dist: requests; extra == 'test-notebook'
|
|
185
|
+
Requires-Dist: traitlets; extra == 'test-notebook'
|
|
134
186
|
Provides-Extra: widget
|
|
135
187
|
Requires-Dist: anywidget; extra == 'widget'
|
|
136
188
|
Requires-Dist: traitlets; extra == 'widget'
|
|
@@ -150,16 +202,14 @@ Description-Content-Type: text/markdown
|
|
|
150
202
|
[](https://mwouts.github.io/itables/apps/dash.html)
|
|
151
203
|
[](https://itables.streamlit.app)
|
|
152
204
|
|
|
153
|
-
This
|
|
205
|
+
This package changes how Pandas and Polars DataFrames are rendered in Python notebooks and applications.
|
|
154
206
|
With `itables` you can display your tables as interactive [DataTables](https://datatables.net/)
|
|
155
207
|
that you can sort, paginate, scroll or filter.
|
|
156
208
|
|
|
157
209
|
ITables is just about how tables are displayed. You can turn it on and off in just two lines,
|
|
158
210
|
with no other impact on your data workflow.
|
|
159
211
|
|
|
160
|
-
|
|
161
|
-
which you must already have if you work with Pandas in Jupyter (add `polars`, `pyarrow` if you
|
|
162
|
-
work with Polars DataFrames).
|
|
212
|
+
Since v2.6.0, ITables has no dependencies. It works out of the box with Pandas or Polars in Jupyter, Dash, Streamlit, or Marimo—you only need these packages installed. The Jupyter Widget is the sole exception, requiring `anywidget`. With Narwhals installed, ITables can also display DataFrames from other libraries like cuDF, Modin or PyArrow.
|
|
163
213
|
|
|
164
214
|
## Documentation
|
|
165
215
|
|
|
@@ -210,7 +260,7 @@ ITables is also available as
|
|
|
210
260
|
## Licence
|
|
211
261
|
|
|
212
262
|
ITables is developed by [Marc Wouts](https://github.com/mwouts) on [GitHub](https://github.com/mwouts/itables),
|
|
213
|
-
under
|
|
263
|
+
under an MIT license.
|
|
214
264
|
|
|
215
265
|
ITables is a wrapper for [datatables.net](https://datatables.net/) which is developed by Allan Jardine
|
|
216
|
-
[(sponsor him!)](https://github.com/sponsors/AllanJard), also under
|
|
266
|
+
[(sponsor him!)](https://github.com/sponsors/AllanJard), also under an MIT license.
|
|
@@ -12,16 +12,14 @@
|
|
|
12
12
|
[](https://mwouts.github.io/itables/apps/dash.html)
|
|
13
13
|
[](https://itables.streamlit.app)
|
|
14
14
|
|
|
15
|
-
This
|
|
15
|
+
This package changes how Pandas and Polars DataFrames are rendered in Python notebooks and applications.
|
|
16
16
|
With `itables` you can display your tables as interactive [DataTables](https://datatables.net/)
|
|
17
17
|
that you can sort, paginate, scroll or filter.
|
|
18
18
|
|
|
19
19
|
ITables is just about how tables are displayed. You can turn it on and off in just two lines,
|
|
20
20
|
with no other impact on your data workflow.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
which you must already have if you work with Pandas in Jupyter (add `polars`, `pyarrow` if you
|
|
24
|
-
work with Polars DataFrames).
|
|
22
|
+
Since v2.6.0, ITables has no dependencies. It works out of the box with Pandas or Polars in Jupyter, Dash, Streamlit, or Marimo—you only need these packages installed. The Jupyter Widget is the sole exception, requiring `anywidget`. With Narwhals installed, ITables can also display DataFrames from other libraries like cuDF, Modin or PyArrow.
|
|
25
23
|
|
|
26
24
|
## Documentation
|
|
27
25
|
|
|
@@ -72,7 +70,7 @@ ITables is also available as
|
|
|
72
70
|
## Licence
|
|
73
71
|
|
|
74
72
|
ITables is developed by [Marc Wouts](https://github.com/mwouts) on [GitHub](https://github.com/mwouts/itables),
|
|
75
|
-
under
|
|
73
|
+
under an MIT license.
|
|
76
74
|
|
|
77
75
|
ITables is a wrapper for [datatables.net](https://datatables.net/) which is developed by Allan Jardine
|
|
78
|
-
[(sponsor him!)](https://github.com/sponsors/AllanJard), also under
|
|
76
|
+
[(sponsor him!)](https://github.com/sponsors/AllanJard), also under an MIT license.
|
|
@@ -10,6 +10,16 @@ from .typing import (
|
|
|
10
10
|
)
|
|
11
11
|
from .version import __version__
|
|
12
12
|
|
|
13
|
+
try:
|
|
14
|
+
from itables import sample_pandas_dfs
|
|
15
|
+
except ImportError:
|
|
16
|
+
sample_pandas_dfs = None
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from itables import sample_polars_dfs
|
|
20
|
+
except ImportError:
|
|
21
|
+
sample_polars_dfs = None
|
|
22
|
+
|
|
13
23
|
__all__ = [
|
|
14
24
|
"__version__",
|
|
15
25
|
"to_html_datatable",
|
|
@@ -24,4 +34,6 @@ __all__ = [
|
|
|
24
34
|
"options",
|
|
25
35
|
"downsample",
|
|
26
36
|
"sample_dfs",
|
|
37
|
+
"sample_pandas_dfs",
|
|
38
|
+
"sample_polars_dfs",
|
|
27
39
|
]
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import math
|
|
3
|
+
import sys
|
|
4
|
+
import warnings
|
|
5
|
+
from typing import Any, Optional, Sequence
|
|
6
|
+
|
|
7
|
+
from .typing import DataFrameOrSeries, get_dataframe_module_name
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _format_pandas_series(x, escape_html: bool) -> Sequence[Any]:
|
|
11
|
+
dtype_kind = x.dtype.kind
|
|
12
|
+
if dtype_kind in ["b", "i"]:
|
|
13
|
+
return x
|
|
14
|
+
|
|
15
|
+
if dtype_kind == "s":
|
|
16
|
+
if escape_html:
|
|
17
|
+
return x
|
|
18
|
+
return [escape_html_chars(i) for i in x]
|
|
19
|
+
|
|
20
|
+
import pandas.io.formats.format as fmt
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
x = fmt.format_array(x._values, None, justify="all", leading_space=False) # type: ignore
|
|
24
|
+
except TypeError:
|
|
25
|
+
# Older versions of Pandas don't have 'leading_space'
|
|
26
|
+
x = fmt.format_array(x._values, None, justify="all") # type: ignore
|
|
27
|
+
|
|
28
|
+
y: Sequence[Any] = x
|
|
29
|
+
if dtype_kind == "f":
|
|
30
|
+
np = sys.modules["numpy"]
|
|
31
|
+
try:
|
|
32
|
+
z = np.array(x).astype(float)
|
|
33
|
+
except ValueError:
|
|
34
|
+
z = x
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
y = [escape_non_finite_float(f) for f in z]
|
|
38
|
+
|
|
39
|
+
if escape_html:
|
|
40
|
+
return [escape_html_chars(i) for i in y]
|
|
41
|
+
|
|
42
|
+
return y
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _format_polars_series(x, escape_html: bool) -> Sequence[Any]:
|
|
46
|
+
"""Format a Polars Series for DataTables display"""
|
|
47
|
+
pl = sys.modules["polars"]
|
|
48
|
+
dtype = x.dtype
|
|
49
|
+
|
|
50
|
+
# Boolean and integer types - return as-is
|
|
51
|
+
if dtype in (
|
|
52
|
+
pl.Boolean,
|
|
53
|
+
pl.Int8,
|
|
54
|
+
pl.Int16,
|
|
55
|
+
pl.Int32,
|
|
56
|
+
pl.Int64,
|
|
57
|
+
pl.UInt8,
|
|
58
|
+
pl.UInt16,
|
|
59
|
+
pl.UInt32,
|
|
60
|
+
pl.UInt64,
|
|
61
|
+
):
|
|
62
|
+
return x.to_list()
|
|
63
|
+
|
|
64
|
+
# Float types - format and handle non-finite values
|
|
65
|
+
if dtype in (pl.Float32, pl.Float64):
|
|
66
|
+
# Round floats according to Polars config
|
|
67
|
+
precision = pl.Config.state().get("set_float_precision")
|
|
68
|
+
if precision is None:
|
|
69
|
+
values = x.to_list()
|
|
70
|
+
else:
|
|
71
|
+
values = x.round(precision).to_list()
|
|
72
|
+
return [escape_non_finite_float(v) for v in values]
|
|
73
|
+
|
|
74
|
+
# Any other type: convert to string
|
|
75
|
+
try:
|
|
76
|
+
formatted = x.cast(str).to_list()
|
|
77
|
+
except pl.exceptions.InvalidOperationError:
|
|
78
|
+
formatted = [str(i) if i is not None else None for i in x.to_list()]
|
|
79
|
+
|
|
80
|
+
if escape_html:
|
|
81
|
+
return [escape_html_chars(i) for i in formatted]
|
|
82
|
+
return formatted
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _format_narwhals_series(x, escape_html: bool) -> Sequence[Any]:
|
|
86
|
+
"""Format a Narwhals Series for DataTables display"""
|
|
87
|
+
nw = sys.modules["narwhals"]
|
|
88
|
+
dtype = x.dtype
|
|
89
|
+
|
|
90
|
+
# Boolean and integer types - return as-is
|
|
91
|
+
if dtype in (
|
|
92
|
+
nw.Boolean,
|
|
93
|
+
nw.Int8,
|
|
94
|
+
nw.Int16,
|
|
95
|
+
nw.Int32,
|
|
96
|
+
nw.Int64,
|
|
97
|
+
nw.UInt8,
|
|
98
|
+
nw.UInt16,
|
|
99
|
+
nw.UInt32,
|
|
100
|
+
nw.UInt64,
|
|
101
|
+
):
|
|
102
|
+
return [v for v in x]
|
|
103
|
+
|
|
104
|
+
# Float types - format and handle non-finite values
|
|
105
|
+
if dtype in (nw.Float32, nw.Float64):
|
|
106
|
+
return [escape_non_finite_float(v) for v in x]
|
|
107
|
+
|
|
108
|
+
formatted = [str(v) for v in x]
|
|
109
|
+
if escape_html:
|
|
110
|
+
return [escape_html_chars(i) for i in formatted]
|
|
111
|
+
return formatted
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def escape_non_finite_float(value: Any) -> Any:
|
|
115
|
+
"""Encode non-finite float values to strings that will be parsed by parseJSON"""
|
|
116
|
+
if not isinstance(value, float):
|
|
117
|
+
return value
|
|
118
|
+
if math.isnan(value):
|
|
119
|
+
return "___NaN___"
|
|
120
|
+
if value == math.inf:
|
|
121
|
+
return "___Infinity___"
|
|
122
|
+
if value == -math.inf:
|
|
123
|
+
return "___-Infinity___"
|
|
124
|
+
return value
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def escape_html_chars(value: Any) -> Any:
|
|
128
|
+
"""Escape HTML special characters"""
|
|
129
|
+
if isinstance(value, str):
|
|
130
|
+
return value.replace("&", r"&").replace("<", r"<").replace(">", r">")
|
|
131
|
+
return value
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def generate_encoder(warn_on_unexpected_types: bool = True) -> Any:
|
|
135
|
+
"""Generate a JSON encoder that can handle special types like numpy"""
|
|
136
|
+
|
|
137
|
+
class TableValuesEncoder(json.JSONEncoder):
|
|
138
|
+
def default(self, o):
|
|
139
|
+
if isinstance(o, (bool, int, float, str)):
|
|
140
|
+
return json.JSONEncoder.default(self, o)
|
|
141
|
+
module = type(o).__module__
|
|
142
|
+
if module == "numpy":
|
|
143
|
+
import numpy as np
|
|
144
|
+
|
|
145
|
+
if isinstance(o, np.bool_):
|
|
146
|
+
return bool(o)
|
|
147
|
+
if isinstance(o, np.integer):
|
|
148
|
+
return int(o)
|
|
149
|
+
if isinstance(o, np.floating):
|
|
150
|
+
return float(o)
|
|
151
|
+
pd = sys.modules.get("pandas", None)
|
|
152
|
+
if pd is not None and o is pd.NA:
|
|
153
|
+
return str(o)
|
|
154
|
+
|
|
155
|
+
if warn_on_unexpected_types:
|
|
156
|
+
warnings.warn(
|
|
157
|
+
f"Unexpected type '{type(o)}' for '{o}'.\n"
|
|
158
|
+
"You can report this warning at https://github.com/mwouts/itables/issues\n"
|
|
159
|
+
"To silence this warning, please run:\n"
|
|
160
|
+
" itables.options.warn_on_unexpected_types = False",
|
|
161
|
+
category=RuntimeWarning,
|
|
162
|
+
)
|
|
163
|
+
return str(o)
|
|
164
|
+
|
|
165
|
+
return TableValuesEncoder
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def datatables_rows(
|
|
169
|
+
df: DataFrameOrSeries,
|
|
170
|
+
column_count: Optional[int] = None,
|
|
171
|
+
warn_on_unexpected_types: bool = False,
|
|
172
|
+
escape_html: bool = True,
|
|
173
|
+
) -> str:
|
|
174
|
+
"""Format the values in the table and return the data, row by row, as requested by DataTables"""
|
|
175
|
+
# We iterate over columns using an index rather than the column name
|
|
176
|
+
# to avoid an issue in case of duplicated column names #89
|
|
177
|
+
if column_count is None or len(df.columns) == column_count:
|
|
178
|
+
empty_columns = []
|
|
179
|
+
else:
|
|
180
|
+
# When the header requires more columns (#141), we append empty columns on the left
|
|
181
|
+
missing_columns = column_count - len(df.columns)
|
|
182
|
+
assert missing_columns > 0
|
|
183
|
+
empty_columns = [[None] * len(df)] * missing_columns
|
|
184
|
+
|
|
185
|
+
df_module = get_dataframe_module_name(df)
|
|
186
|
+
if df_module == "pandas":
|
|
187
|
+
formatted_columns = [
|
|
188
|
+
_format_pandas_series(x, escape_html) for _, x in df.items()
|
|
189
|
+
]
|
|
190
|
+
elif df_module == "polars":
|
|
191
|
+
formatted_columns = [
|
|
192
|
+
_format_polars_series(df[col], escape_html) for col in df.columns
|
|
193
|
+
]
|
|
194
|
+
else:
|
|
195
|
+
# Other DataFrame types are handled via Narwhals, and are expected
|
|
196
|
+
# to have been converted to Narwhals already (except in tests)
|
|
197
|
+
import narwhals as nw
|
|
198
|
+
|
|
199
|
+
df = nw.from_native(df, eager_only=True, allow_series=True)
|
|
200
|
+
|
|
201
|
+
formatted_columns = [
|
|
202
|
+
_format_narwhals_series(df[col], escape_html) for col in df.columns
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
data = list(zip(*(empty_columns + formatted_columns)))
|
|
206
|
+
|
|
207
|
+
return json.dumps(
|
|
208
|
+
data,
|
|
209
|
+
cls=generate_encoder(warn_on_unexpected_types),
|
|
210
|
+
allow_nan=False,
|
|
211
|
+
)
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import math
|
|
2
2
|
from typing import Union
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
from itables.typing import (
|
|
5
|
+
DataFrameModuleName,
|
|
6
|
+
DataFrameOrSeries,
|
|
7
|
+
get_dataframe_module_name,
|
|
8
|
+
)
|
|
5
9
|
|
|
6
|
-
from itables.typing import DataFrameOrSeries
|
|
7
10
|
|
|
11
|
+
def nbytes(df: DataFrameOrSeries, df_module_name: DataFrameModuleName = None) -> int:
|
|
12
|
+
"""Return an estimate for number of bytes used by the dataframe"""
|
|
13
|
+
if df_module_name is None:
|
|
14
|
+
df_module_name = get_dataframe_module_name(df)
|
|
15
|
+
assert df_module_name is not None
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
try:
|
|
17
|
+
if df_module_name in ["pandas", "numpy"]:
|
|
11
18
|
return sum(x.values.nbytes for _, x in df.items())
|
|
12
|
-
|
|
13
|
-
# Polars DataFrame
|
|
19
|
+
else:
|
|
20
|
+
# Polars or Narwhalified DataFrame
|
|
14
21
|
return df.estimated_size()
|
|
15
22
|
|
|
16
23
|
|
|
@@ -34,19 +41,31 @@ def as_nbytes(mem: Union[int, float, str]) -> int:
|
|
|
34
41
|
|
|
35
42
|
def downsample(
|
|
36
43
|
df: DataFrameOrSeries,
|
|
44
|
+
df_module_name: DataFrameModuleName = None,
|
|
37
45
|
max_rows: int = 0,
|
|
38
46
|
max_columns: int = 0,
|
|
39
47
|
max_bytes: Union[int, str] = 0,
|
|
40
48
|
) -> tuple[DataFrameOrSeries, str]:
|
|
41
49
|
"""Return a subset of the dataframe that fits the limits"""
|
|
42
|
-
|
|
50
|
+
if df_module_name is None:
|
|
51
|
+
df_module_name = get_dataframe_module_name(df)
|
|
52
|
+
|
|
53
|
+
org_rows, org_columns, org_bytes = (
|
|
54
|
+
len(df),
|
|
55
|
+
len(df.columns),
|
|
56
|
+
nbytes(df, df_module_name),
|
|
57
|
+
)
|
|
43
58
|
max_bytes_numeric = as_nbytes(max_bytes)
|
|
44
59
|
df = _downsample(
|
|
45
|
-
df,
|
|
60
|
+
df,
|
|
61
|
+
df_module_name,
|
|
62
|
+
max_rows=max_rows,
|
|
63
|
+
max_columns=max_columns,
|
|
64
|
+
max_bytes=max_bytes_numeric,
|
|
46
65
|
)
|
|
47
66
|
|
|
48
67
|
if len(df) < org_rows or len(df.columns) < org_columns:
|
|
49
|
-
|
|
68
|
+
downsampled = '<a href="https://mwouts.github.io/itables/downsampling.html">downsampled</a>'
|
|
50
69
|
reasons = []
|
|
51
70
|
if org_rows > max_rows > 0:
|
|
52
71
|
reasons.append("maxRows={}".format(max_rows))
|
|
@@ -55,16 +74,14 @@ def downsample(
|
|
|
55
74
|
if org_bytes > max_bytes_numeric > 0:
|
|
56
75
|
reasons.append("maxBytes={}".format(max_bytes))
|
|
57
76
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
" and ".join(reasons),
|
|
65
|
-
)
|
|
77
|
+
if len(df.columns) < org_columns:
|
|
78
|
+
downsampled_warning = f"{downsampled} from {org_rows:,d}x{org_columns:,d} to {len(df):,d}x{len(df.columns):,d} as {' and '.join(reasons)}"
|
|
79
|
+
else:
|
|
80
|
+
downsampled_warning = (
|
|
81
|
+
f"{downsampled} from {org_rows:,d} rows as {' and '.join(reasons)}"
|
|
82
|
+
)
|
|
66
83
|
|
|
67
|
-
return df,
|
|
84
|
+
return df, downsampled_warning
|
|
68
85
|
|
|
69
86
|
return df, ""
|
|
70
87
|
|
|
@@ -98,41 +115,42 @@ def shrink_towards_target_aspect_ratio(
|
|
|
98
115
|
return int(rows * row_shrink_factor), int(columns * column_shrink_factor)
|
|
99
116
|
|
|
100
117
|
|
|
101
|
-
def _downsample(
|
|
118
|
+
def _downsample(
|
|
119
|
+
df,
|
|
120
|
+
df_module_name: DataFrameModuleName,
|
|
121
|
+
max_rows=0,
|
|
122
|
+
max_columns=0,
|
|
123
|
+
max_bytes=0,
|
|
124
|
+
target_aspect_ratio=None,
|
|
125
|
+
):
|
|
102
126
|
"""Implementation of downsample - may be called recursively"""
|
|
103
127
|
if len(df) > max_rows > 0:
|
|
104
128
|
second_half = max_rows // 2
|
|
105
129
|
first_half = max_rows - second_half
|
|
106
130
|
assert first_half >= second_half
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
131
|
+
rows = list(range(first_half)) + list(range(len(df) - second_half, len(df)))
|
|
132
|
+
if df_module_name == "pandas":
|
|
133
|
+
df = df.iloc[rows]
|
|
134
|
+
elif df_module_name == "polars":
|
|
135
|
+
df = df[rows]
|
|
112
136
|
else:
|
|
113
|
-
|
|
114
|
-
df = df.iloc[:first_half]
|
|
115
|
-
except AttributeError:
|
|
116
|
-
df = df.head(first_half)
|
|
137
|
+
raise TypeError(f"Unsupported DataFrame type: {df_module_name}")
|
|
117
138
|
|
|
118
139
|
if len(df.columns) > max_columns > 0:
|
|
119
140
|
second_half = max_columns // 2
|
|
120
141
|
first_half = max_columns - second_half
|
|
121
142
|
assert first_half >= second_half
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
143
|
+
columns = list(range(first_half)) + list(
|
|
144
|
+
range(len(df.columns) - second_half, len(df.columns))
|
|
145
|
+
)
|
|
146
|
+
if df_module_name == "pandas":
|
|
147
|
+
df = df.iloc[:, columns]
|
|
148
|
+
elif df_module_name == "polars":
|
|
149
|
+
df = df[[df.columns[i] for i in columns]]
|
|
129
150
|
else:
|
|
130
|
-
|
|
131
|
-
df = df.iloc[:, :first_half]
|
|
132
|
-
else:
|
|
133
|
-
df = df[df.columns[:first_half]]
|
|
151
|
+
raise TypeError(f"Unsupported DataFrame type: {df_module_name}")
|
|
134
152
|
|
|
135
|
-
df_nbytes = nbytes(df)
|
|
153
|
+
df_nbytes = nbytes(df, df_module_name)
|
|
136
154
|
if df_nbytes > max_bytes > 0:
|
|
137
155
|
if target_aspect_ratio is None:
|
|
138
156
|
if max_rows > 0 and max_columns > 0:
|
|
@@ -149,19 +167,16 @@ def _downsample(df, max_rows=0, max_columns=0, max_bytes=0, target_aspect_ratio=
|
|
|
149
167
|
|
|
150
168
|
if max_rows > 0 and max_columns > 0:
|
|
151
169
|
return _downsample(
|
|
152
|
-
df,
|
|
170
|
+
df,
|
|
171
|
+
df_module_name,
|
|
172
|
+
max_rows,
|
|
173
|
+
max_columns,
|
|
174
|
+
max_bytes,
|
|
175
|
+
target_aspect_ratio,
|
|
153
176
|
)
|
|
154
177
|
|
|
155
178
|
# max_bytes is smaller than the average size of one cell
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
else:
|
|
160
|
-
import polars as pl # noqa
|
|
161
|
-
|
|
162
|
-
assert isinstance(df, pl.DataFrame)
|
|
163
|
-
|
|
164
|
-
df = pl.DataFrame({df.columns[0]: ["..."]})
|
|
165
|
-
return df
|
|
179
|
+
# return a single cell with "..."
|
|
180
|
+
return type(df)({df.columns[0]: ["..."]})
|
|
166
181
|
|
|
167
182
|
return df
|