solarwindpy 0.1.1__py3-none-any.whl → 0.1.4__py3-none-any.whl
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.
Potentially problematic release.
This version of solarwindpy might be problematic. Click here for more details.
- plans/github-issues-migration/0-Overview.md +153 -103
- plans/github-issues-migration/1-Foundation-Label-System.md +97 -65
- plans/github-issues-migration/2-Plan-Creation-System.md +189 -0
- plans/github-issues-migration/3-CLI-Integration-Documentation.md +231 -0
- plans/github-issues-migration/4-Plan-Closeout-Validation.md +199 -0
- solarwindpy/fitfunctions/plots.py +10 -2
- solarwindpy/plotting/hist2d.py +6 -6
- {solarwindpy-0.1.1.dist-info → solarwindpy-0.1.4.dist-info}/METADATA +36 -19
- {solarwindpy-0.1.1.dist-info → solarwindpy-0.1.4.dist-info}/RECORD +15 -12
- tests/core/test_spacecraft.py +4 -4
- tests/plotting/test_hist2d_pandas_compat.py +409 -0
- tests/plotting/test_visual_validation.py +1 -1
- solarwindpy/Untitled.ipynb +0 -54
- {solarwindpy-0.1.1.dist-info → solarwindpy-0.1.4.dist-info}/WHEEL +0 -0
- {solarwindpy-0.1.1.dist-info → solarwindpy-0.1.4.dist-info}/licenses/LICENSE.rst +0 -0
- {solarwindpy-0.1.1.dist-info → solarwindpy-0.1.4.dist-info}/top_level.txt +0 -0
solarwindpy/plotting/hist2d.py
CHANGED
|
@@ -240,9 +240,9 @@ class Hist2D(base.PlotWithZdata, base.CbarMaker, AggPlot):
|
|
|
240
240
|
if axnorm is None:
|
|
241
241
|
pass
|
|
242
242
|
elif axnorm == "c":
|
|
243
|
-
agg = agg.divide(agg.
|
|
243
|
+
agg = agg.divide(agg.groupby(level="x").max(), level="x")
|
|
244
244
|
elif axnorm == "r":
|
|
245
|
-
agg = agg.divide(agg.
|
|
245
|
+
agg = agg.divide(agg.groupby(level="y").max(), level="y")
|
|
246
246
|
elif axnorm == "t":
|
|
247
247
|
agg = agg.divide(agg.max())
|
|
248
248
|
elif axnorm == "d":
|
|
@@ -265,7 +265,7 @@ class Hist2D(base.PlotWithZdata, base.CbarMaker, AggPlot):
|
|
|
265
265
|
|
|
266
266
|
elif axnorm == "cd":
|
|
267
267
|
# raise NotImplementedError("Need to verify data alignment, especially `dx` values and index")
|
|
268
|
-
N = agg.
|
|
268
|
+
N = agg.groupby(level="x").sum()
|
|
269
269
|
dy = pd.IntervalIndex(
|
|
270
270
|
agg.index.get_level_values("y").unique()
|
|
271
271
|
).sort_values()
|
|
@@ -275,7 +275,7 @@ class Hist2D(base.PlotWithZdata, base.CbarMaker, AggPlot):
|
|
|
275
275
|
|
|
276
276
|
elif axnorm == "rd":
|
|
277
277
|
# raise NotImplementedError("Need to verify data alignment, especially `dx` values and index")
|
|
278
|
-
N = agg.
|
|
278
|
+
N = agg.groupby(level="y").sum()
|
|
279
279
|
dx = pd.IntervalIndex(
|
|
280
280
|
agg.index.get_level_values("x").unique()
|
|
281
281
|
).sort_values()
|
|
@@ -286,9 +286,9 @@ class Hist2D(base.PlotWithZdata, base.CbarMaker, AggPlot):
|
|
|
286
286
|
elif hasattr(axnorm, "__iter__"):
|
|
287
287
|
kind, fcn = axnorm
|
|
288
288
|
if kind == "c":
|
|
289
|
-
agg = agg.divide(agg.
|
|
289
|
+
agg = agg.divide(agg.groupby(level="x").agg(fcn), level="x")
|
|
290
290
|
elif kind == "r":
|
|
291
|
-
agg = agg.divide(agg.
|
|
291
|
+
agg = agg.divide(agg.groupby(level="y").agg(fcn), level="y")
|
|
292
292
|
else:
|
|
293
293
|
raise ValueError(f"Unrecognized axnorm with function ({kind}, {fcn})")
|
|
294
294
|
else:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: solarwindpy
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Python package for solar wind data analysis.
|
|
5
5
|
Author-email: "B. L. Alterman" <blaltermanphd@gmail.com>
|
|
6
6
|
License: LICENSE
|
|
@@ -53,17 +53,17 @@ Classifier: Topic :: Scientific/Engineering :: Physics
|
|
|
53
53
|
Requires-Python: <4,>=3.10
|
|
54
54
|
Description-Content-Type: text/x-rst
|
|
55
55
|
License-File: LICENSE.rst
|
|
56
|
-
Requires-Dist: numpy
|
|
57
|
-
Requires-Dist: scipy
|
|
58
|
-
Requires-Dist: pandas
|
|
59
|
-
Requires-Dist: numexpr
|
|
60
|
-
Requires-Dist: bottleneck
|
|
61
|
-
Requires-Dist: h5py
|
|
62
|
-
Requires-Dist: pyyaml
|
|
63
|
-
Requires-Dist: matplotlib
|
|
64
|
-
Requires-Dist: astropy
|
|
65
|
-
Requires-Dist: numba
|
|
66
|
-
Requires-Dist: tabulate
|
|
56
|
+
Requires-Dist: numpy<2.0,>=1.22
|
|
57
|
+
Requires-Dist: scipy>=1.10
|
|
58
|
+
Requires-Dist: pandas>=1.5
|
|
59
|
+
Requires-Dist: numexpr>=2.8
|
|
60
|
+
Requires-Dist: bottleneck>=1.3
|
|
61
|
+
Requires-Dist: h5py>=3.8
|
|
62
|
+
Requires-Dist: pyyaml>=6.0
|
|
63
|
+
Requires-Dist: matplotlib>=3.5
|
|
64
|
+
Requires-Dist: astropy>=5.0
|
|
65
|
+
Requires-Dist: numba>=0.57
|
|
66
|
+
Requires-Dist: tabulate>=0.9
|
|
67
67
|
Provides-Extra: dev
|
|
68
68
|
Requires-Dist: pytest>=7.4.4; extra == "dev"
|
|
69
69
|
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
@@ -90,6 +90,23 @@ SolarWindPy
|
|
|
90
90
|
|
|
91
91
|
Python data analysis tools for solar wind measurements.
|
|
92
92
|
|
|
93
|
+
Quick Start
|
|
94
|
+
-----------
|
|
95
|
+
|
|
96
|
+
After installation, import the package and create a plasma object:
|
|
97
|
+
|
|
98
|
+
.. code-block:: python
|
|
99
|
+
|
|
100
|
+
import solarwindpy as swp
|
|
101
|
+
|
|
102
|
+
# Load plasma data (example with sample data)
|
|
103
|
+
plasma = swp.Plasma()
|
|
104
|
+
|
|
105
|
+
# Access ion species and magnetic field data
|
|
106
|
+
print(plasma.data.columns) # View available measurements
|
|
107
|
+
|
|
108
|
+
See the documentation for detailed usage examples and API reference.
|
|
109
|
+
|
|
93
110
|
Installation
|
|
94
111
|
============
|
|
95
112
|
|
|
@@ -116,17 +133,17 @@ Development
|
|
|
116
133
|
|
|
117
134
|
.. code-block:: bash
|
|
118
135
|
|
|
119
|
-
conda env create -f solarwindpy
|
|
120
|
-
conda activate solarwindpy
|
|
136
|
+
conda env create -f solarwindpy.yml # Python 3.10+
|
|
137
|
+
conda activate solarwindpy
|
|
121
138
|
pip install -e .
|
|
122
139
|
|
|
123
140
|
Alternatively generate the environment from ``requirements-dev.txt``:
|
|
124
141
|
|
|
125
142
|
.. code-block:: bash
|
|
126
143
|
|
|
127
|
-
python scripts/requirements_to_conda_env.py --name solarwindpy
|
|
128
|
-
conda env create -f solarwindpy
|
|
129
|
-
conda activate solarwindpy
|
|
144
|
+
python scripts/requirements_to_conda_env.py --name solarwindpy
|
|
145
|
+
conda env create -f solarwindpy.yml
|
|
146
|
+
conda activate solarwindpy
|
|
130
147
|
pip install -e .
|
|
131
148
|
|
|
132
149
|
3. Run the test suite with ``pytest``:
|
|
@@ -171,8 +188,8 @@ See `CITATION.rst`_ for instructions on citing SolarWindPy.
|
|
|
171
188
|
.. _LICENSE.rst: ./LICENSE.rst
|
|
172
189
|
.. _CITATION.rst: ./CITATION.rst
|
|
173
190
|
|
|
174
|
-
.. |Build Status| image:: https://github.com/blalterman/SolarWindPy/actions/workflows/
|
|
175
|
-
:target: https://github.com/blalterman/SolarWindPy/actions/workflows/
|
|
191
|
+
.. |Build Status| image:: https://github.com/blalterman/SolarWindPy/actions/workflows/ci-master.yml/badge.svg?branch=master
|
|
192
|
+
:target: https://github.com/blalterman/SolarWindPy/actions/workflows/ci-master.yml
|
|
176
193
|
.. |Docs Status| image:: https://readthedocs.org/projects/solarwindpy/badge/?version=latest
|
|
177
194
|
:target: https://solarwindpy.readthedocs.io/en/latest/?badge=latest
|
|
178
195
|
.. |License| image:: https://img.shields.io/badge/License-BSD%203--Clause-blue.svg
|
|
@@ -202,10 +202,13 @@ plans/documentation-workflow-fix/2-Configuration-Setup.md,sha256=alGWMMObb3aQp_k
|
|
|
202
202
|
plans/documentation-workflow-fix/3-Pre-commit-Integration.md,sha256=7M-iyg1ifPikuWLYG8-M5EN9U6EnQektoD8J0tjTKfI,9066
|
|
203
203
|
plans/documentation-workflow-fix/4-Workflow-Improvements.md,sha256=xs3dU5OCWksszqmXX44Y6qrLCNpoK_Q6b756Rsyqa1E,13130
|
|
204
204
|
plans/documentation-workflow-fix/5-Documentation-and-Training.md,sha256=adnQVs7IhiAj6zNlzl_xjIfSVT35N95fDLlFEwlFdKQ,12725
|
|
205
|
-
plans/github-issues-migration/0-Overview.md,sha256=
|
|
206
|
-
plans/github-issues-migration/1-Foundation-Label-System.md,sha256=
|
|
205
|
+
plans/github-issues-migration/0-Overview.md,sha256=u5-VQp8mE7zOOwixP6Z2ktXJC2HkaQ6BwKGQIGTmkvk,26114
|
|
206
|
+
plans/github-issues-migration/1-Foundation-Label-System.md,sha256=QVMyHVzj3xYQOmWBKdAVk-zYAOHJgG9-GYVUC3POJDE,11066
|
|
207
207
|
plans/github-issues-migration/2-Migration-Tool-Rewrite.md,sha256=vXztSKQHKILm3IDgmvND1GgPqwfpXyGMA8lBfXiqY3E,11398
|
|
208
|
+
plans/github-issues-migration/2-Plan-Creation-System.md,sha256=uk8mLFSVCQO45AErRzwb_K0vJXf2yfX5lM1Tj4CFXnE,9612
|
|
208
209
|
plans/github-issues-migration/3-CLI-Integration-Automation.md,sha256=PBvyP41Oa2lP7LBGMSkLXD0wfbZh1H1wyykRH-fZtCg,7504
|
|
210
|
+
plans/github-issues-migration/3-CLI-Integration-Documentation.md,sha256=0-YtPNYCEMUMtdoQob15-H7KTA3wOccTjE5rXgJDR2k,12117
|
|
211
|
+
plans/github-issues-migration/4-Plan-Closeout-Validation.md,sha256=SSLeNdAhgxYQAFzvt2PnQjrdkqO2q0cJem1JJa8flGI,10366
|
|
209
212
|
plans/github-issues-migration/4-Validated-Migration.md,sha256=A0F6ybse7_OXUdJNAi49QDIkw8GoIBfFnhagyDZbI3Q,12036
|
|
210
213
|
plans/github-issues-migration/5-Documentation-Training.md,sha256=1s6fSOZxe9SmAotH9xdZzUrd1iLTHm1LN92s46rIBqw,7967
|
|
211
214
|
plans/github-issues-migration/6-Closeout.md,sha256=hrk93iv6AUPFULRx_YavqaJGQfn5vjhu-eAGBtpsYlo,7466
|
|
@@ -281,7 +284,6 @@ plans/tests-audit/artifacts/TEST_INVENTORY.md,sha256=fU4HaDHXsOPYMm41Jw4g9oG5OsT
|
|
|
281
284
|
plans/tests-audit/artifacts/test_discovery_analysis.py,sha256=W57F6dZE8SVBmuoiQ7ccyNpoYE3OmgeOAZ-Z57gYpF0,9739
|
|
282
285
|
plans/tests-audit/artifacts/test_parser.py,sha256=74PhaairFxZYgPFhPNe0Xgc_rj7mCOpNfYz4p9kKLiI,15910
|
|
283
286
|
solarwindpy/README.md,sha256=54qoMZ3EDkwYIthS_fgolKE63_t5h6tl1fAu8W6sPWQ,63
|
|
284
|
-
solarwindpy/Untitled.ipynb,sha256=25BEnYCUG4OiVgZjGRdJQBQRb7oUtl0R7mkZGiTkvfs,1589
|
|
285
287
|
solarwindpy/__init__.py,sha256=41v4M-8UGLsWvG-y-QW1x6BBYYtHxi3WwhKyoW6eyuE,1490
|
|
286
288
|
solarwindpy/core/__init__.py,sha256=gNQSJd3_NGaVg1cqgYEmP7a4AxcfJpEBB_9qEqcxwZM,489
|
|
287
289
|
solarwindpy/core/alfvenic_turbulence.py,sha256=l2EChzYpAuuUFys1robjx9CEV9qY6eboIxIOTYXmosw,25730
|
|
@@ -298,7 +300,7 @@ solarwindpy/fitfunctions/exponentials.py,sha256=5NtpKMcns9BpD3jpLszw2Kn86Dt9Uhv4
|
|
|
298
300
|
solarwindpy/fitfunctions/gaussians.py,sha256=db3yj89taBgHCjMF_GoxUlwJlJBjAfgH37rbCtZQZxc,8217
|
|
299
301
|
solarwindpy/fitfunctions/lines.py,sha256=crs5CbdtMpz6liTeKcBW2_2o9dQClYPV5QXSrd5YXc8,2806
|
|
300
302
|
solarwindpy/fitfunctions/moyal.py,sha256=cfoNGJ4-0tHjJUMKoVf7WXo4Zf_UwtYIa9b8WZd385k,2043
|
|
301
|
-
solarwindpy/fitfunctions/plots.py,sha256=
|
|
303
|
+
solarwindpy/fitfunctions/plots.py,sha256=nnu1zLxkmHOe0HqDD_yZWIRhvZ6rvVG0hZakxsPvELk,24615
|
|
302
304
|
solarwindpy/fitfunctions/power_laws.py,sha256=rEt-eO8k5HwFhqN1WKLF4fb-ADsQzFiFi6RfK-i-y90,6065
|
|
303
305
|
solarwindpy/fitfunctions/tex_info.py,sha256=UXI-JFe6oIn4wNmknm6woyJzNVYolW4OiWHvaSCApHg,18919
|
|
304
306
|
solarwindpy/fitfunctions/trend_fits.py,sha256=ZAsHGpMf5ATcCTHkaLglsTqDsTA6wcY2UoWlN3YKv3U,15353
|
|
@@ -309,7 +311,7 @@ solarwindpy/plotting/__init__.py,sha256=xcyOoL2mt9uO0HBgtyXbZMF2pWP1OvL9EbG8fl5P
|
|
|
309
311
|
solarwindpy/plotting/agg_plot.py,sha256=NhEx2Kh4dDGxBdMjJVsCuMwRyIvtWS5lNi-TlbbFrnM,16381
|
|
310
312
|
solarwindpy/plotting/base.py,sha256=FhyLeQnAgLOyjdJAWLIswusCLvFlyQQxTSufnMEw_cQ,14345
|
|
311
313
|
solarwindpy/plotting/hist1d.py,sha256=tynZSnFfjw53pVFki6EXbti3NP5q62zp8xQD6NFhDrE,12721
|
|
312
|
-
solarwindpy/plotting/hist2d.py,sha256=
|
|
314
|
+
solarwindpy/plotting/hist2d.py,sha256=AEDcotXoWsGrojr6GsvGCRMClZzXGu7j79eXqkJ0hqs,33957
|
|
313
315
|
solarwindpy/plotting/histograms.py,sha256=vlViwlEohbWmnyXyEkaS5SW33NhCwz_WGsg5p4UgMsE,60538
|
|
314
316
|
solarwindpy/plotting/orbits.py,sha256=s7kTtaWSD6fDKjtLnfIgAHOoNtAQc_1NR8a-Rj9d7-w,17011
|
|
315
317
|
solarwindpy/plotting/scatter.py,sha256=m8tCkOwapRppOREfR8Gvw6UZxRxmWgclkjaxbKPIYUI,2746
|
|
@@ -337,7 +339,7 @@ solarwindpy/solar_activity/sunspot_number/sidc.py,sha256=zTktSZxPSNtdQ1jrRl7V76k
|
|
|
337
339
|
solarwindpy/solar_activity/sunspot_number/ssn_extrema.csv,sha256=5OJ5iEMLWIYYnBqihYpDBAOV9oiwE3hLmVzEsUUlfr4,2101
|
|
338
340
|
solarwindpy/solar_activity/sunspot_number/ssn_extrema.csv.silso,sha256=iQq8Ea0EMU1LZj2wtJQeRd2Sw-PW58wGSJtVpR8hgmE,2102
|
|
339
341
|
solarwindpy/tools/__init__.py,sha256=hoLkvWMHNcDFlxdjKF8wov4MNzHVCayheiRcrYCZ7RM,4602
|
|
340
|
-
solarwindpy-0.1.
|
|
342
|
+
solarwindpy-0.1.4.dist-info/licenses/LICENSE.rst,sha256=LnFDisrg2iF2g1WG7hg3UyezM7BHvixBj5u-KJy7HqI,1531
|
|
341
343
|
tests/__init__.py,sha256=QMXatjFn0cwLVfV0syJuu_di6qEr8qYe-WwhwUBssLg,32
|
|
342
344
|
tests/conftest.py,sha256=sPZrGyQIdS6-i9LGbXOors23BId5p7TCoyJaF8whxOw,441
|
|
343
345
|
tests/test_circular_imports.py,sha256=S94K4RRKggn_9rJutfyTXBTMtPxbG3DQNcR6HMHOHdU,14664
|
|
@@ -353,7 +355,7 @@ tests/core/test_ions.py,sha256=AXBhG5ANI2cKr1rfzbbFwKFGBghznAXdi3jfOsIZq4M,10434
|
|
|
353
355
|
tests/core/test_plasma.py,sha256=bI_rDyWpnM2G78M4KRqEfwi82i_xRuqPx4WaNSKTGHA,103241
|
|
354
356
|
tests/core/test_plasma_io.py,sha256=j0TrV5T2rPMcKX7tbIzWO_DWTwmJ96hnwrmazFOGL4k,384
|
|
355
357
|
tests/core/test_quantities.py,sha256=_9cP3HdVr0JMUDABZiwTSnBokSpZbWagqFQ-OSrL2F8,16955
|
|
356
|
-
tests/core/test_spacecraft.py,sha256=
|
|
358
|
+
tests/core/test_spacecraft.py,sha256=sYXtPVgGaGZoRHP6rgoE8jv-ZdBfbPtYH0Ft0WgNkWM,6855
|
|
357
359
|
tests/core/test_units_constants.py,sha256=Yelx_3F6Q1IH-yOLJDgcsF4khYu6nZhVY7svm3xnHAk,511
|
|
358
360
|
tests/data/epoch.csv,sha256=ILnMSC8BUwtYzOedeldDFOtB4MYEvZgumCT8LZLoa90,72
|
|
359
361
|
tests/data/plasma.csv,sha256=jtT4msNHX_muY7YWRQkWIrqU140nmnLLLstHSoAhs_w,652
|
|
@@ -373,6 +375,7 @@ tests/plotting/__init__.py,sha256=5R4PdTYEQvYbU0yuz2a1EcFlzic8QUHnl_ojA9tmFzY,24
|
|
|
373
375
|
tests/plotting/test_agg_plot.py,sha256=lBrNSgwEHcDtCD6YM8_yV_hFdxlOCCWaxIV_dnF-ZJI,21156
|
|
374
376
|
tests/plotting/test_base.py,sha256=vyHl4vSn71B7BvcFcLNCjc1FCCvELFKcBkbt1PhXSu4,24618
|
|
375
377
|
tests/plotting/test_fixtures_utilities.py,sha256=SvjOixxm-eXvSkEtjOAMVn_UmHKkEB2Kc9W0fI7ZzlU,25483
|
|
378
|
+
tests/plotting/test_hist2d_pandas_compat.py,sha256=RYm9xCc7XyHv8AJffcfH3Kqrr9Y3bg2dEMYD5rIqsO0,15205
|
|
376
379
|
tests/plotting/test_histograms.py,sha256=QSyA0lgc8x8t5Niwtuqveco8DV8kGHfEoV5ZhAjNfRk,20007
|
|
377
380
|
tests/plotting/test_integration.py,sha256=_YNuQ2x_7gx_m2-8JWOmMBrJimalFll53-phEtb1yjU,22964
|
|
378
381
|
tests/plotting/test_orbits.py,sha256=AjS5mk-69LjmJvnrIcX3NcQW6dyVGEy0ZmeoYhQ79Ws,16381
|
|
@@ -381,7 +384,7 @@ tests/plotting/test_scatter.py,sha256=Neco0LuCsRfDB7nwyMSQsnqxlvpUi2lCRZlkXGQZ2u
|
|
|
381
384
|
tests/plotting/test_select_data_from_figure.py,sha256=Ndz-mtsB8FZXrzOb8IWhMynWhpt8CqaQSOKVpugK_co,42427
|
|
382
385
|
tests/plotting/test_spiral.py,sha256=GrKRxjdIa5f9wnS0EZtkjS13h5OW8mvBIlgZY7UtSfY,20494
|
|
383
386
|
tests/plotting/test_tools.py,sha256=fptKZFwCMq5vOIeoZikTqoXI87dGr_kiOwAEXTVX_vE,20383
|
|
384
|
-
tests/plotting/test_visual_validation.py,sha256=
|
|
387
|
+
tests/plotting/test_visual_validation.py,sha256=rRu_Fj1d22GIG6k1ByC2-nCKgO5ONuETsaLcSaSDEE8,14700
|
|
385
388
|
tests/plotting/labels/__init__.py,sha256=p1Ttswdbhq9cxnuaQlQ1LSPbb-92OBwzLlWcCz5ACFg,24
|
|
386
389
|
tests/plotting/labels/test_chemistry.py,sha256=AawZXmN3f72NKrkgybDm342NvepKLS55-jaNDRsjjmE,7950
|
|
387
390
|
tests/plotting/labels/test_composition.py,sha256=ps1Mnmey8UfxvCs-mNR1P-1j9kgf2ImLO7gX3PmuEw0,11939
|
|
@@ -403,7 +406,7 @@ tests/solar_activity/sunspot_number/test_sidc.py,sha256=VDTC3KIZNrs8HqSp7qNnz1K4
|
|
|
403
406
|
tests/solar_activity/sunspot_number/test_sidc_id.py,sha256=xP_dLw99dPxAKhW-YqcextUAvNNLrRC9VgWVm0JxBPo,8153
|
|
404
407
|
tests/solar_activity/sunspot_number/test_sidc_loader.py,sha256=4LmOmyt9OLas2oUT9iOa7TQ8bn5iyB9a_HqupIH05OQ,10271
|
|
405
408
|
tests/solar_activity/sunspot_number/test_ssn_extrema.py,sha256=KOliGq5ErhKYWp_qbXmBqZU4EmHS-bOgmBcAngIkQfQ,14747
|
|
406
|
-
solarwindpy-0.1.
|
|
407
|
-
solarwindpy-0.1.
|
|
408
|
-
solarwindpy-0.1.
|
|
409
|
-
solarwindpy-0.1.
|
|
409
|
+
solarwindpy-0.1.4.dist-info/METADATA,sha256=8QgE83e8dUPDpgXWvvit0dxiwmc-Gmx7BoEorRfCjXA,6688
|
|
410
|
+
solarwindpy-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
411
|
+
solarwindpy-0.1.4.dist-info/top_level.txt,sha256=kKjGFlQvA-UE4SpU-m7df_Y1_eGhtRs_eqy6Gq96v8c,24
|
|
412
|
+
solarwindpy-0.1.4.dist-info/RECORD,,
|
tests/core/test_spacecraft.py
CHANGED
|
@@ -16,7 +16,7 @@ from solarwindpy import spacecraft
|
|
|
16
16
|
pd.set_option("mode.chained_assignment", "raise")
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
class
|
|
19
|
+
class SpacecraftTestBase(ABC):
|
|
20
20
|
@classmethod
|
|
21
21
|
def setUpClass(cls):
|
|
22
22
|
data = base.TestData()
|
|
@@ -47,7 +47,7 @@ class TestBase(ABC):
|
|
|
47
47
|
# cls.set_object_testing()
|
|
48
48
|
# print("Done with TestBase", flush=True)
|
|
49
49
|
|
|
50
|
-
# super(
|
|
50
|
+
# super(SpacecraftTestBase, cls).setUpClass()
|
|
51
51
|
# # print(cls.data.iloc[:, :7])
|
|
52
52
|
# # print(cls.data.columns.values)
|
|
53
53
|
# cls.data = cls.spacecraft_data
|
|
@@ -127,7 +127,7 @@ class TestBase(ABC):
|
|
|
127
127
|
pdt.assert_series_equal(dist, ot.distance2sun)
|
|
128
128
|
|
|
129
129
|
|
|
130
|
-
class TestWind(
|
|
130
|
+
class TestWind(SpacecraftTestBase, TestCase):
|
|
131
131
|
@classmethod
|
|
132
132
|
def set_object_testing(cls):
|
|
133
133
|
data = cls.data.xs("gse", axis=1, level="M")
|
|
@@ -164,7 +164,7 @@ class TestWind(TestBase, TestCase):
|
|
|
164
164
|
self.object_testing.carrington
|
|
165
165
|
|
|
166
166
|
|
|
167
|
-
class TestPSP(
|
|
167
|
+
class TestPSP(SpacecraftTestBase, TestCase):
|
|
168
168
|
@classmethod
|
|
169
169
|
def set_object_testing(cls):
|
|
170
170
|
p = cls.data.xs("pos_HCI", axis=1, level="M")
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Regression tests for pandas 2.3.1+ compatibility in hist2d.py.
|
|
3
|
+
|
|
4
|
+
These tests verify that the axis normalization methods work correctly
|
|
5
|
+
after replacing deprecated .max(level=...) syntax with .groupby(level=...).max().
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from solarwindpy.plotting.hist2d import Hist2D
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestHist2DPandasCompatibility:
|
|
16
|
+
"""Test pandas 2.3.1+ compatibility for Hist2D axis normalization."""
|
|
17
|
+
|
|
18
|
+
def setup_method(self):
|
|
19
|
+
"""Set up test data for each test."""
|
|
20
|
+
np.random.seed(42)
|
|
21
|
+
n = 1000
|
|
22
|
+
|
|
23
|
+
# Create test data with known distributions
|
|
24
|
+
self.x_data = pd.Series(np.random.normal(0, 1, n), name="x")
|
|
25
|
+
self.y_data = pd.Series(np.random.normal(0, 1, n), name="y")
|
|
26
|
+
self.z_data = pd.Series(np.random.uniform(0, 10, n), name="z")
|
|
27
|
+
|
|
28
|
+
def test_column_normalize(self):
|
|
29
|
+
"""Test column normalize (axnorm='c')."""
|
|
30
|
+
hist = Hist2D(self.x_data, self.y_data, self.z_data, nbins=10)
|
|
31
|
+
hist.set_axnorm("c")
|
|
32
|
+
|
|
33
|
+
# Get normalized aggregation
|
|
34
|
+
agg = hist.agg()
|
|
35
|
+
agg_unstacked = agg.unstack("x")
|
|
36
|
+
|
|
37
|
+
# Check that max value in each column is 1.0 (or NaN)
|
|
38
|
+
for col in agg_unstacked.columns:
|
|
39
|
+
col_max = agg_unstacked[col].max()
|
|
40
|
+
if not pd.isna(col_max):
|
|
41
|
+
assert np.isclose(
|
|
42
|
+
col_max, 1.0, atol=1e-10
|
|
43
|
+
), f"Column {col} max is {col_max}, expected 1.0"
|
|
44
|
+
|
|
45
|
+
# Check that all non-NaN values are between 0 and 1
|
|
46
|
+
non_nan_values = agg.dropna()
|
|
47
|
+
assert (
|
|
48
|
+
non_nan_values >= 0
|
|
49
|
+
).all(), "Found negative values after column normalization"
|
|
50
|
+
assert (
|
|
51
|
+
non_nan_values <= 1.0001
|
|
52
|
+
).all(), "Found values > 1 after column normalization"
|
|
53
|
+
|
|
54
|
+
def test_row_normalize(self):
|
|
55
|
+
"""Test row normalize (axnorm='r')."""
|
|
56
|
+
hist = Hist2D(self.x_data, self.y_data, self.z_data, nbins=10)
|
|
57
|
+
hist.set_axnorm("r")
|
|
58
|
+
|
|
59
|
+
# Get normalized aggregation
|
|
60
|
+
agg = hist.agg()
|
|
61
|
+
agg_unstacked = agg.unstack("x")
|
|
62
|
+
|
|
63
|
+
# Check that max value in each row is 1.0 (or NaN)
|
|
64
|
+
for row_idx in agg_unstacked.index:
|
|
65
|
+
row_max = agg_unstacked.loc[row_idx].max()
|
|
66
|
+
if not pd.isna(row_max):
|
|
67
|
+
assert np.isclose(
|
|
68
|
+
row_max, 1.0, atol=1e-10
|
|
69
|
+
), f"Row {row_idx} max is {row_max}, expected 1.0"
|
|
70
|
+
|
|
71
|
+
# Check that all non-NaN values are between 0 and 1
|
|
72
|
+
non_nan_values = agg.dropna()
|
|
73
|
+
assert (
|
|
74
|
+
non_nan_values >= 0
|
|
75
|
+
).all(), "Found negative values after row normalization"
|
|
76
|
+
assert (
|
|
77
|
+
non_nan_values <= 1.0001
|
|
78
|
+
).all(), "Found values > 1 after row normalization"
|
|
79
|
+
|
|
80
|
+
def test_total_normalize(self):
|
|
81
|
+
"""Test total normalize (axnorm='t')."""
|
|
82
|
+
hist = Hist2D(self.x_data, self.y_data, self.z_data, nbins=10)
|
|
83
|
+
hist.set_axnorm("t")
|
|
84
|
+
|
|
85
|
+
# Get normalized aggregation
|
|
86
|
+
agg = hist.agg()
|
|
87
|
+
|
|
88
|
+
# Check that max value overall is 1.0
|
|
89
|
+
max_val = agg.max()
|
|
90
|
+
assert np.isclose(
|
|
91
|
+
max_val, 1.0, atol=1e-10
|
|
92
|
+
), f"Total max is {max_val}, expected 1.0"
|
|
93
|
+
|
|
94
|
+
# Check that all non-NaN values are between 0 and 1
|
|
95
|
+
non_nan_values = agg.dropna()
|
|
96
|
+
assert (
|
|
97
|
+
non_nan_values >= 0
|
|
98
|
+
).all(), "Found negative values after total normalization"
|
|
99
|
+
assert (
|
|
100
|
+
non_nan_values <= 1.0001
|
|
101
|
+
).all(), "Found values > 1 after total normalization"
|
|
102
|
+
|
|
103
|
+
def test_density_normalize(self):
|
|
104
|
+
"""Test density normalize (axnorm='d').
|
|
105
|
+
|
|
106
|
+
This should create a true 2D probability density function where
|
|
107
|
+
the integral over the entire domain equals 1.
|
|
108
|
+
"""
|
|
109
|
+
hist = Hist2D(self.x_data, self.y_data, self.z_data, nbins=10)
|
|
110
|
+
hist.set_axnorm("d")
|
|
111
|
+
|
|
112
|
+
# Get normalized aggregation
|
|
113
|
+
agg = hist.agg()
|
|
114
|
+
|
|
115
|
+
# Get bin widths for integration
|
|
116
|
+
x_intervals = hist.intervals["x"]
|
|
117
|
+
y_intervals = hist.intervals["y"]
|
|
118
|
+
|
|
119
|
+
# Calculate dx and dy for each bin
|
|
120
|
+
dx_values = pd.Series(
|
|
121
|
+
[interval.length for interval in x_intervals], index=x_intervals
|
|
122
|
+
)
|
|
123
|
+
dy_values = pd.Series(
|
|
124
|
+
[interval.length for interval in y_intervals], index=y_intervals
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Compute the integral: sum(agg * dx * dy)
|
|
128
|
+
agg_unstacked = agg.unstack("x")
|
|
129
|
+
total_integral = 0
|
|
130
|
+
for y_idx, y_interval in enumerate(agg_unstacked.index):
|
|
131
|
+
for x_idx, x_interval in enumerate(agg_unstacked.columns):
|
|
132
|
+
value = agg_unstacked.iloc[y_idx, x_idx]
|
|
133
|
+
if not pd.isna(value):
|
|
134
|
+
dx = dx_values[x_interval]
|
|
135
|
+
dy = dy_values[y_interval]
|
|
136
|
+
total_integral += value * dx * dy
|
|
137
|
+
|
|
138
|
+
# The integral should be close to 1
|
|
139
|
+
assert np.isclose(
|
|
140
|
+
total_integral, 1.0, atol=0.01
|
|
141
|
+
), f"Density integral is {total_integral}, expected 1.0"
|
|
142
|
+
|
|
143
|
+
# Values should be non-negative
|
|
144
|
+
non_nan_values = agg.dropna()
|
|
145
|
+
assert (
|
|
146
|
+
non_nan_values >= 0
|
|
147
|
+
).all(), "Found negative values after density normalization"
|
|
148
|
+
|
|
149
|
+
def test_pdfs_in_each_column(self):
|
|
150
|
+
"""Test PDFs in each column (axnorm='cd').
|
|
151
|
+
|
|
152
|
+
This creates PDFs in each column, so integrating over y for each x should give 1.
|
|
153
|
+
"""
|
|
154
|
+
hist = Hist2D(self.x_data, self.y_data, self.z_data, nbins=10)
|
|
155
|
+
hist.set_axnorm("cd")
|
|
156
|
+
|
|
157
|
+
# Get normalized aggregation
|
|
158
|
+
agg = hist.agg()
|
|
159
|
+
agg_unstacked = agg.unstack("x")
|
|
160
|
+
|
|
161
|
+
# Get y bin widths for integration
|
|
162
|
+
y_intervals = hist.intervals["y"]
|
|
163
|
+
dy_values = pd.Series(
|
|
164
|
+
[interval.length for interval in y_intervals], index=y_intervals
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# For each column, integrate over y
|
|
168
|
+
for col in agg_unstacked.columns:
|
|
169
|
+
col_data = agg_unstacked[col]
|
|
170
|
+
integral = 0
|
|
171
|
+
for y_idx, y_interval in enumerate(col_data.index):
|
|
172
|
+
value = col_data.iloc[y_idx]
|
|
173
|
+
if not pd.isna(value):
|
|
174
|
+
dy = dy_values[y_interval]
|
|
175
|
+
integral += value * dy
|
|
176
|
+
|
|
177
|
+
# Each column should integrate to 1 (if it has data)
|
|
178
|
+
if integral > 0: # Only check columns with data
|
|
179
|
+
assert np.isclose(
|
|
180
|
+
integral, 1.0, atol=0.01
|
|
181
|
+
), f"Column {col} PDF integral is {integral}, expected 1.0"
|
|
182
|
+
|
|
183
|
+
# Values should be non-negative
|
|
184
|
+
non_nan_values = agg.dropna()
|
|
185
|
+
assert (
|
|
186
|
+
non_nan_values >= 0
|
|
187
|
+
).all(), "Found negative values after PDFs in each column"
|
|
188
|
+
|
|
189
|
+
def test_pdfs_in_each_row(self):
|
|
190
|
+
"""Test PDFs in each row (axnorm='rd').
|
|
191
|
+
|
|
192
|
+
This creates PDFs in each row, so integrating over x for each y should give 1.
|
|
193
|
+
"""
|
|
194
|
+
hist = Hist2D(self.x_data, self.y_data, self.z_data, nbins=10)
|
|
195
|
+
hist.set_axnorm("rd")
|
|
196
|
+
|
|
197
|
+
# Get normalized aggregation
|
|
198
|
+
agg = hist.agg()
|
|
199
|
+
agg_unstacked = agg.unstack("x")
|
|
200
|
+
|
|
201
|
+
# Get x bin widths for integration
|
|
202
|
+
x_intervals = hist.intervals["x"]
|
|
203
|
+
dx_values = pd.Series(
|
|
204
|
+
[interval.length for interval in x_intervals], index=x_intervals
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# For each row, integrate over x
|
|
208
|
+
for row in agg_unstacked.index:
|
|
209
|
+
row_data = agg_unstacked.loc[row]
|
|
210
|
+
integral = 0
|
|
211
|
+
for x_idx, x_interval in enumerate(row_data.index):
|
|
212
|
+
value = row_data.iloc[x_idx]
|
|
213
|
+
if not pd.isna(value):
|
|
214
|
+
dx = dx_values[x_interval]
|
|
215
|
+
integral += value * dx
|
|
216
|
+
|
|
217
|
+
# Each row should integrate to 1 (if it has data)
|
|
218
|
+
if integral > 0: # Only check rows with data
|
|
219
|
+
assert np.isclose(
|
|
220
|
+
integral, 1.0, atol=0.01
|
|
221
|
+
), f"Row {row} PDF integral is {integral}, expected 1.0"
|
|
222
|
+
|
|
223
|
+
# Values should be non-negative
|
|
224
|
+
non_nan_values = agg.dropna()
|
|
225
|
+
assert (
|
|
226
|
+
non_nan_values >= 0
|
|
227
|
+
).all(), "Found negative values after PDFs in each row"
|
|
228
|
+
|
|
229
|
+
def test_no_normalization(self):
|
|
230
|
+
"""Test that no normalization (axnorm=None) works correctly."""
|
|
231
|
+
hist = Hist2D(self.x_data, self.y_data, self.z_data, nbins=10)
|
|
232
|
+
hist.set_axnorm(None)
|
|
233
|
+
|
|
234
|
+
# Get aggregation without normalization
|
|
235
|
+
agg = hist.agg()
|
|
236
|
+
|
|
237
|
+
# Values should be the raw aggregated z values
|
|
238
|
+
assert agg is not None
|
|
239
|
+
assert not agg.isna().all(), "All values are NaN without normalization"
|
|
240
|
+
|
|
241
|
+
# Check that values are in reasonable range for raw z data (0-10)
|
|
242
|
+
non_nan_values = agg.dropna()
|
|
243
|
+
assert non_nan_values.min() >= 0, "Found negative values in raw aggregation"
|
|
244
|
+
assert (
|
|
245
|
+
non_nan_values.max() <= 10.1
|
|
246
|
+
), "Found unexpectedly large values in raw aggregation"
|
|
247
|
+
|
|
248
|
+
def test_edge_case_single_bin(self):
|
|
249
|
+
"""Test normalization with data that falls into a single bin."""
|
|
250
|
+
# Create data that falls into one bin
|
|
251
|
+
x_single = pd.Series([0.5] * 100, name="x")
|
|
252
|
+
y_single = pd.Series([0.5] * 100, name="y")
|
|
253
|
+
z_single = pd.Series(np.random.uniform(1, 2, 100), name="z")
|
|
254
|
+
|
|
255
|
+
hist = Hist2D(x_single, y_single, z_single, nbins=10)
|
|
256
|
+
hist.set_axnorm("c")
|
|
257
|
+
|
|
258
|
+
# Get normalized aggregation
|
|
259
|
+
agg = hist.agg()
|
|
260
|
+
|
|
261
|
+
# Should have mostly NaN except for the single bin
|
|
262
|
+
non_nan_count = agg.notna().sum()
|
|
263
|
+
assert non_nan_count == 1, f"Expected 1 non-NaN value, got {non_nan_count}"
|
|
264
|
+
|
|
265
|
+
# The single value should be 1.0 after normalization
|
|
266
|
+
non_nan_value = agg.dropna().iloc[0]
|
|
267
|
+
assert np.isclose(
|
|
268
|
+
non_nan_value, 1.0, atol=1e-10
|
|
269
|
+
), f"Single bin value is {non_nan_value}, expected 1.0"
|
|
270
|
+
|
|
271
|
+
def test_edge_case_with_nans(self):
|
|
272
|
+
"""Test normalization with NaN values in input data."""
|
|
273
|
+
# Add some NaN values to the data
|
|
274
|
+
x_with_nan = self.x_data.copy()
|
|
275
|
+
y_with_nan = self.y_data.copy()
|
|
276
|
+
z_with_nan = self.z_data.copy()
|
|
277
|
+
|
|
278
|
+
# Insert NaNs at random positions
|
|
279
|
+
nan_indices = np.random.choice(len(x_with_nan), 50, replace=False)
|
|
280
|
+
x_with_nan.iloc[nan_indices] = np.nan
|
|
281
|
+
y_with_nan.iloc[nan_indices[:25]] = np.nan
|
|
282
|
+
z_with_nan.iloc[nan_indices[25:]] = np.nan
|
|
283
|
+
|
|
284
|
+
hist = Hist2D(x_with_nan, y_with_nan, z_with_nan, nbins=10)
|
|
285
|
+
hist.set_axnorm("c")
|
|
286
|
+
|
|
287
|
+
# Should handle NaNs gracefully
|
|
288
|
+
agg = hist.agg()
|
|
289
|
+
assert agg is not None
|
|
290
|
+
|
|
291
|
+
# Check that non-NaN values are properly normalized
|
|
292
|
+
non_nan_values = agg.dropna()
|
|
293
|
+
if len(non_nan_values) > 0:
|
|
294
|
+
assert (non_nan_values >= 0).all(), "Found negative values with NaN input"
|
|
295
|
+
assert (non_nan_values <= 1.0001).all(), "Found values > 1 with NaN input"
|
|
296
|
+
|
|
297
|
+
def test_count_aggregation_with_column_normalize(self):
|
|
298
|
+
"""Test column normalize with count aggregation (no z values)."""
|
|
299
|
+
# Create hist2d without z values (count aggregation)
|
|
300
|
+
hist = Hist2D(self.x_data, self.y_data, nbins=10)
|
|
301
|
+
hist.set_axnorm("c")
|
|
302
|
+
|
|
303
|
+
# Get normalized aggregation
|
|
304
|
+
agg = hist.agg()
|
|
305
|
+
agg_unstacked = agg.unstack("x")
|
|
306
|
+
|
|
307
|
+
# Check that max value in each column is 1.0 (or NaN)
|
|
308
|
+
for col in agg_unstacked.columns:
|
|
309
|
+
col_max = agg_unstacked[col].max()
|
|
310
|
+
if not pd.isna(col_max):
|
|
311
|
+
assert np.isclose(
|
|
312
|
+
col_max, 1.0, atol=1e-10
|
|
313
|
+
), f"Column {col} max is {col_max}, expected 1.0"
|
|
314
|
+
|
|
315
|
+
def test_log_scale_with_normalization(self):
|
|
316
|
+
"""Test normalization with log-scaled data."""
|
|
317
|
+
# Use positive data for log scale
|
|
318
|
+
x_positive = pd.Series(np.random.uniform(1, 100, 1000), name="x")
|
|
319
|
+
y_positive = pd.Series(np.random.uniform(1, 100, 1000), name="y")
|
|
320
|
+
z_positive = pd.Series(np.random.uniform(1, 10, 1000), name="z")
|
|
321
|
+
|
|
322
|
+
hist = Hist2D(
|
|
323
|
+
x_positive, y_positive, z_positive, nbins=10, logx=True, logy=True
|
|
324
|
+
)
|
|
325
|
+
hist.set_axnorm("c")
|
|
326
|
+
|
|
327
|
+
# Get normalized aggregation
|
|
328
|
+
agg = hist.agg()
|
|
329
|
+
|
|
330
|
+
# Check normalization works with log scale
|
|
331
|
+
assert agg is not None
|
|
332
|
+
assert not agg.isna().all(), "All values are NaN with log scale"
|
|
333
|
+
|
|
334
|
+
# Check that values are properly normalized
|
|
335
|
+
non_nan_values = agg.dropna()
|
|
336
|
+
assert (non_nan_values >= 0).all(), "Found negative values with log scale"
|
|
337
|
+
assert (non_nan_values <= 1.0001).all(), "Found values > 1 with log scale"
|
|
338
|
+
|
|
339
|
+
def test_all_documented_normalizations(self):
|
|
340
|
+
"""Test that all documented normalization options work without error."""
|
|
341
|
+
documented_options = [
|
|
342
|
+
(None, "no normalization"),
|
|
343
|
+
("c", "column normalize"),
|
|
344
|
+
("r", "row normalize"),
|
|
345
|
+
("t", "total normalize"),
|
|
346
|
+
("d", "density normalize"),
|
|
347
|
+
("cd", "PDFs in each column"),
|
|
348
|
+
("rd", "PDFs in each row"),
|
|
349
|
+
]
|
|
350
|
+
|
|
351
|
+
for option, description in documented_options:
|
|
352
|
+
hist = Hist2D(self.x_data, self.y_data, self.z_data, nbins=10)
|
|
353
|
+
hist.set_axnorm(option)
|
|
354
|
+
|
|
355
|
+
# Should be able to get aggregation without error
|
|
356
|
+
agg = hist.agg()
|
|
357
|
+
assert (
|
|
358
|
+
agg is not None
|
|
359
|
+
), f"Failed to get aggregation with axnorm={option} ({description})"
|
|
360
|
+
|
|
361
|
+
# Should have at least some non-NaN values
|
|
362
|
+
if option is not None: # None means no normalization
|
|
363
|
+
non_nan_count = agg.notna().sum()
|
|
364
|
+
assert (
|
|
365
|
+
non_nan_count > 0
|
|
366
|
+
), f"No non-NaN values with axnorm={option} ({description})"
|
|
367
|
+
|
|
368
|
+
def test_density_normalize_with_counts(self):
|
|
369
|
+
"""Test density normalize with count data (no z values).
|
|
370
|
+
|
|
371
|
+
This should create a proper 2D probability density where integral = 1.
|
|
372
|
+
"""
|
|
373
|
+
hist = Hist2D(self.x_data, self.y_data, nbins=10)
|
|
374
|
+
hist.set_axnorm("d")
|
|
375
|
+
|
|
376
|
+
# Get normalized aggregation
|
|
377
|
+
agg = hist.agg()
|
|
378
|
+
|
|
379
|
+
# Get bin widths for integration
|
|
380
|
+
x_intervals = hist.intervals["x"]
|
|
381
|
+
y_intervals = hist.intervals["y"]
|
|
382
|
+
|
|
383
|
+
# Calculate dx and dy for each bin
|
|
384
|
+
dx_values = pd.Series(
|
|
385
|
+
[interval.length for interval in x_intervals], index=x_intervals
|
|
386
|
+
)
|
|
387
|
+
dy_values = pd.Series(
|
|
388
|
+
[interval.length for interval in y_intervals], index=y_intervals
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Compute the integral
|
|
392
|
+
agg_unstacked = agg.unstack("x")
|
|
393
|
+
total_integral = 0
|
|
394
|
+
for y_idx, y_interval in enumerate(agg_unstacked.index):
|
|
395
|
+
for x_idx, x_interval in enumerate(agg_unstacked.columns):
|
|
396
|
+
value = agg_unstacked.iloc[y_idx, x_idx]
|
|
397
|
+
if not pd.isna(value):
|
|
398
|
+
dx = dx_values[x_interval]
|
|
399
|
+
dy = dy_values[y_interval]
|
|
400
|
+
total_integral += value * dx * dy
|
|
401
|
+
|
|
402
|
+
# The integral should be close to 1
|
|
403
|
+
assert np.isclose(
|
|
404
|
+
total_integral, 1.0, atol=0.01
|
|
405
|
+
), f"Count density integral is {total_integral}, expected 1.0"
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
if __name__ == "__main__":
|
|
409
|
+
pytest.main([__file__, "-v"])
|