From 9772ae312291e3a68ead66cb20c5055b9826326e Mon Sep 17 00:00:00 2001 From: Jim Pivarski Date: Fri, 10 Jul 2020 14:28:15 -0500 Subject: [PATCH] Connect to boost-histogram and hist. (#46) * Connect to boost-histogram and hist. * Assign behaviors from any superclasses. * Move imports for 'extras' dependencies to a submodule. * Converted TH1F into NumPy histogram. * 1D and 2D histograms can both be converted to NumPy format. * Converts 1D histograms to boost-histogram. * Figuring out what metadata is good. * Resolved metadata question; added categorical bins. * Exactly correct TProfile errors (all four cases). * Wrangling API and duplication. * Punt on interpretation of boost-histogram WeightedMean. * Fix flake8 error. * Every histogram type must have a 'values_errors'. * Remember to skip the boost-histogram tests if you don't have boost-histogram. --- tests/test_0045-lazy-arrays-1.py | 3 + tests/test_0046-histograms-bh-hist.py | 2841 +++++++++++++++++++++++++ uproot4/__init__.py | 2 +- uproot4/behaviors/TH1.py | 129 ++ uproot4/behaviors/TH2.py | 68 + uproot4/behaviors/TH2Poly.py | 22 + uproot4/behaviors/TH3.py | 74 + uproot4/behaviors/TProfile.py | 162 ++ uproot4/behaviors/TProfile2D.py | 29 + uproot4/behaviors/TProfile3D.py | 31 + uproot4/compression.py | 59 +- uproot4/deserialization.py | 16 +- uproot4/extras.py | 211 ++ uproot4/interpretation/library.py | 72 +- uproot4/model.py | 9 + uproot4/models/TArray.py | 7 +- uproot4/models/THashList.py | 3 + uproot4/source/xrootd.py | 38 +- 18 files changed, 3621 insertions(+), 155 deletions(-) create mode 100644 tests/test_0046-histograms-bh-hist.py create mode 100644 uproot4/behaviors/TH1.py create mode 100644 uproot4/behaviors/TH2.py create mode 100644 uproot4/behaviors/TH2Poly.py create mode 100644 uproot4/behaviors/TH3.py create mode 100644 uproot4/behaviors/TProfile.py create mode 100644 uproot4/behaviors/TProfile2D.py create mode 100644 uproot4/behaviors/TProfile3D.py create mode 100644 uproot4/extras.py diff --git a/tests/test_0045-lazy-arrays-1.py b/tests/test_0045-lazy-arrays-1.py index b0b9f31dc..42303a6a5 100644 --- a/tests/test_0045-lazy-arrays-1.py +++ b/tests/test_0045-lazy-arrays-1.py @@ -23,6 +23,7 @@ def test_array_cast(): def test_branch_pluralization(): + awkward1 = pytest.importorskip("awkward1") with uproot4.open(skhep_testdata.data_path("uproot-Zmumu.root"))[ "events/px1" ] as px1: @@ -155,6 +156,7 @@ def test_awkward_pluralization(): def test_dask(): + awkward1 = pytest.importorskip("awkward1") dask_array = pytest.importorskip("dask.array") files = ( skhep_testdata.data_path("uproot-sample-6.20.04-uncompressed.root").replace( @@ -175,6 +177,7 @@ def test_dask(): def test_daskframe(): + awkward1 = pytest.importorskip("awkward1") dask_frame = pytest.importorskip("dask.dataframe") files = ( skhep_testdata.data_path("uproot-sample-6.20.04-uncompressed.root").replace( diff --git a/tests/test_0046-histograms-bh-hist.py b/tests/test_0046-histograms-bh-hist.py new file mode 100644 index 000000000..c92d27a64 --- /dev/null +++ b/tests/test_0046-histograms-bh-hist.py @@ -0,0 +1,2841 @@ +# BSD 3-Clause License; see https://github.com/scikit-hep/uproot4/blob/master/LICENSE + +from __future__ import absolute_import + +import numpy +import pytest +import skhep_testdata + +import uproot4 +import uproot4.behaviors.TProfile + + +def test_numpy_1d(): + with uproot4.open(skhep_testdata.data_path("uproot-hepdata-example.root")) as f: + values, edges = f["hpx"].np + assert values.tolist() == [ + 2.0, + 2.0, + 3.0, + 1.0, + 1.0, + 2.0, + 4.0, + 6.0, + 12.0, + 8.0, + 9.0, + 15.0, + 15.0, + 31.0, + 35.0, + 40.0, + 64.0, + 64.0, + 81.0, + 108.0, + 124.0, + 156.0, + 165.0, + 209.0, + 262.0, + 297.0, + 392.0, + 432.0, + 466.0, + 521.0, + 604.0, + 657.0, + 788.0, + 903.0, + 1079.0, + 1135.0, + 1160.0, + 1383.0, + 1458.0, + 1612.0, + 1770.0, + 1868.0, + 1861.0, + 1946.0, + 2114.0, + 2175.0, + 2207.0, + 2273.0, + 2276.0, + 2329.0, + 2325.0, + 2381.0, + 2417.0, + 2364.0, + 2284.0, + 2188.0, + 2164.0, + 2130.0, + 1940.0, + 1859.0, + 1763.0, + 1700.0, + 1611.0, + 1459.0, + 1390.0, + 1237.0, + 1083.0, + 1046.0, + 888.0, + 752.0, + 742.0, + 673.0, + 555.0, + 533.0, + 366.0, + 378.0, + 272.0, + 256.0, + 200.0, + 174.0, + 132.0, + 118.0, + 100.0, + 89.0, + 86.0, + 39.0, + 37.0, + 25.0, + 23.0, + 20.0, + 16.0, + 14.0, + 9.0, + 13.0, + 8.0, + 2.0, + 2.0, + 6.0, + 1.0, + 0.0, + 1.0, + 4.0, + ] + + assert edges.tolist() == [ + -numpy.inf, + -4.0, + -3.92, + -3.84, + -3.76, + -3.68, + -3.6, + -3.52, + -3.44, + -3.36, + -3.2800000000000002, + -3.2, + -3.12, + -3.04, + -2.96, + -2.88, + -2.8, + -2.7199999999999998, + -2.6399999999999997, + -2.56, + -2.48, + -2.4, + -2.3200000000000003, + -2.24, + -2.16, + -2.08, + -2.0, + -1.92, + -1.8399999999999999, + -1.7599999999999998, + -1.6800000000000002, + -1.6, + -1.52, + -1.44, + -1.3599999999999999, + -1.2799999999999998, + -1.1999999999999997, + -1.12, + -1.04, + -0.96, + -0.8799999999999999, + -0.7999999999999998, + -0.7199999999999998, + -0.6400000000000001, + -0.56, + -0.48, + -0.3999999999999999, + -0.31999999999999984, + -0.23999999999999977, + -0.16000000000000014, + -0.08000000000000007, + 0.0, + 0.08000000000000007, + 0.16000000000000014, + 0.2400000000000002, + 0.3200000000000003, + 0.40000000000000036, + 0.4800000000000004, + 0.5600000000000005, + 0.6399999999999997, + 0.7199999999999998, + 0.7999999999999998, + 0.8799999999999999, + 0.96, + 1.04, + 1.12, + 1.2000000000000002, + 1.2800000000000002, + 1.3600000000000003, + 1.4400000000000004, + 1.5200000000000005, + 1.6000000000000005, + 1.6799999999999997, + 1.7599999999999998, + 1.8399999999999999, + 1.92, + 2.0, + 2.08, + 2.16, + 2.24, + 2.3200000000000003, + 2.4000000000000004, + 2.4800000000000004, + 2.5600000000000005, + 2.6400000000000006, + 2.7199999999999998, + 2.8, + 2.88, + 2.96, + 3.04, + 3.12, + 3.2, + 3.2800000000000002, + 3.3600000000000003, + 3.4400000000000004, + 3.5200000000000005, + 3.6000000000000005, + 3.6799999999999997, + 3.76, + 3.84, + 3.92, + 4.0, + numpy.inf, + ] + + f["hpx"].values_errors() + + +def test_numpy_2d(): + with uproot4.open(skhep_testdata.data_path("uproot-hepdata-example.root")) as f: + values, xedges, yedges = f["hpxpy"].np + + assert values.tolist() == [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 2.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.0, + 1.0, + 2.0, + 2.0, + 0.0, + 1.0, + 0.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 3.0, + 1.0, + 0.0, + 1.0, + 2.0, + 1.0, + 0.0, + 1.0, + 1.0, + 1.0, + 2.0, + 1.0, + 2.0, + 3.0, + 0.0, + 1.0, + 1.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 2.0, + 0.0, + 2.0, + 4.0, + 2.0, + 5.0, + 2.0, + 2.0, + 1.0, + 5.0, + 3.0, + 4.0, + 4.0, + 1.0, + 4.0, + 1.0, + 2.0, + 1.0, + 2.0, + 1.0, + 0.0, + 0.0, + 1.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 3.0, + 1.0, + 6.0, + 1.0, + 3.0, + 6.0, + 3.0, + 6.0, + 9.0, + 6.0, + 9.0, + 5.0, + 2.0, + 4.0, + 4.0, + 1.0, + 6.0, + 1.0, + 2.0, + 0.0, + 1.0, + 1.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 2.0, + 0.0, + 0.0, + 3.0, + 4.0, + 5.0, + 5.0, + 6.0, + 9.0, + 17.0, + 8.0, + 13.0, + 13.0, + 14.0, + 11.0, + 13.0, + 6.0, + 10.0, + 5.0, + 4.0, + 5.0, + 7.0, + 2.0, + 1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 4.0, + 4.0, + 5.0, + 11.0, + 7.0, + 8.0, + 13.0, + 17.0, + 16.0, + 28.0, + 16.0, + 22.0, + 24.0, + 22.0, + 18.0, + 17.0, + 10.0, + 6.0, + 4.0, + 8.0, + 6.0, + 1.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 5.0, + 1.0, + 6.0, + 9.0, + 9.0, + 22.0, + 16.0, + 20.0, + 29.0, + 33.0, + 21.0, + 35.0, + 34.0, + 19.0, + 32.0, + 30.0, + 28.0, + 17.0, + 14.0, + 8.0, + 11.0, + 4.0, + 2.0, + 2.0, + 2.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 0.0, + 3.0, + 2.0, + 4.0, + 6.0, + 11.0, + 10.0, + 18.0, + 20.0, + 34.0, + 39.0, + 42.0, + 51.0, + 47.0, + 68.0, + 60.0, + 41.0, + 47.0, + 37.0, + 43.0, + 27.0, + 22.0, + 15.0, + 16.0, + 11.0, + 4.0, + 1.0, + 0.0, + 3.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 3.0, + 2.0, + 7.0, + 6.0, + 8.0, + 21.0, + 23.0, + 44.0, + 53.0, + 60.0, + 73.0, + 95.0, + 82.0, + 68.0, + 86.0, + 88.0, + 72.0, + 66.0, + 52.0, + 40.0, + 28.0, + 27.0, + 15.0, + 17.0, + 8.0, + 6.0, + 4.0, + 2.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + 2.0, + 1.0, + 2.0, + 7.0, + 5.0, + 14.0, + 20.0, + 28.0, + 32.0, + 57.0, + 58.0, + 74.0, + 93.0, + 93.0, + 106.0, + 95.0, + 129.0, + 126.0, + 103.0, + 98.0, + 75.0, + 86.0, + 57.0, + 33.0, + 35.0, + 22.0, + 19.0, + 8.0, + 5.0, + 3.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 4.0, + 9.0, + 7.0, + 12.0, + 14.0, + 36.0, + 31.0, + 54.0, + 60.0, + 88.0, + 94.0, + 104.0, + 134.0, + 150.0, + 142.0, + 179.0, + 120.0, + 135.0, + 119.0, + 108.0, + 91.0, + 56.0, + 38.0, + 43.0, + 20.0, + 15.0, + 12.0, + 5.0, + 1.0, + 1.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 2.0, + 1.0, + 4.0, + 11.0, + 5.0, + 21.0, + 47.0, + 39.0, + 69.0, + 100.0, + 106.0, + 145.0, + 169.0, + 164.0, + 199.0, + 190.0, + 210.0, + 197.0, + 209.0, + 141.0, + 162.0, + 123.0, + 87.0, + 64.0, + 39.0, + 34.0, + 16.0, + 15.0, + 9.0, + 4.0, + 3.0, + 4.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + ], + [ + 1.0, + 0.0, + 0.0, + 0.0, + 2.0, + 1.0, + 4.0, + 8.0, + 12.0, + 16.0, + 26.0, + 40.0, + 48.0, + 81.0, + 149.0, + 131.0, + 171.0, + 222.0, + 231.0, + 241.0, + 260.0, + 270.0, + 260.0, + 200.0, + 203.0, + 155.0, + 150.0, + 111.0, + 67.0, + 61.0, + 27.0, + 26.0, + 28.0, + 12.0, + 4.0, + 5.0, + 0.0, + 1.0, + 1.0, + 0.0, + 0.0, + 2.0, + ], + [ + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 3.0, + 5.0, + 7.0, + 13.0, + 23.0, + 46.0, + 49.0, + 88.0, + 106.0, + 148.0, + 179.0, + 211.0, + 232.0, + 298.0, + 284.0, + 287.0, + 275.0, + 281.0, + 284.0, + 233.0, + 211.0, + 172.0, + 142.0, + 84.0, + 86.0, + 46.0, + 26.0, + 24.0, + 13.0, + 10.0, + 3.0, + 3.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 1.0, + 1.0, + 0.0, + 2.0, + 4.0, + 2.0, + 8.0, + 16.0, + 28.0, + 42.0, + 61.0, + 87.0, + 105.0, + 155.0, + 215.0, + 253.0, + 250.0, + 353.0, + 334.0, + 363.0, + 370.0, + 344.0, + 341.0, + 303.0, + 268.0, + 197.0, + 169.0, + 119.0, + 99.0, + 80.0, + 49.0, + 25.0, + 22.0, + 11.0, + 1.0, + 2.0, + 1.0, + 2.0, + 1.0, + 0.0, + 0.0, + ], + [ + 1.0, + 0.0, + 0.0, + 0.0, + 2.0, + 3.0, + 7.0, + 10.0, + 25.0, + 28.0, + 47.0, + 61.0, + 80.0, + 126.0, + 201.0, + 206.0, + 281.0, + 361.0, + 341.0, + 403.0, + 403.0, + 416.0, + 398.0, + 360.0, + 345.0, + 278.0, + 247.0, + 206.0, + 131.0, + 83.0, + 69.0, + 54.0, + 30.0, + 22.0, + 13.0, + 5.0, + 3.0, + 2.0, + 3.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 4.0, + 7.0, + 17.0, + 16.0, + 25.0, + 52.0, + 70.0, + 102.0, + 132.0, + 198.0, + 274.0, + 321.0, + 346.0, + 420.0, + 479.0, + 448.0, + 445.0, + 411.0, + 391.0, + 348.0, + 339.0, + 237.0, + 202.0, + 153.0, + 131.0, + 71.0, + 51.0, + 39.0, + 19.0, + 16.0, + 11.0, + 4.0, + 4.0, + 1.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 4.0, + 9.0, + 10.0, + 18.0, + 37.0, + 44.0, + 87.0, + 92.0, + 140.0, + 198.0, + 236.0, + 311.0, + 375.0, + 432.0, + 445.0, + 478.0, + 497.0, + 476.0, + 412.0, + 360.0, + 331.0, + 255.0, + 211.0, + 165.0, + 131.0, + 76.0, + 45.0, + 34.0, + 23.0, + 12.0, + 2.0, + 4.0, + 2.0, + 0.0, + 2.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 1.0, + 2.0, + 1.0, + 3.0, + 13.0, + 13.0, + 21.0, + 41.0, + 51.0, + 95.0, + 108.0, + 148.0, + 216.0, + 261.0, + 354.0, + 363.0, + 425.0, + 438.0, + 446.0, + 449.0, + 436.0, + 424.0, + 367.0, + 339.0, + 279.0, + 209.0, + 151.0, + 107.0, + 79.0, + 46.0, + 28.0, + 21.0, + 9.0, + 5.0, + 8.0, + 1.0, + 2.0, + 3.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 4.0, + 2.0, + 3.0, + 10.0, + 17.0, + 22.0, + 41.0, + 53.0, + 90.0, + 100.0, + 137.0, + 207.0, + 243.0, + 317.0, + 349.0, + 404.0, + 408.0, + 439.0, + 440.0, + 488.0, + 443.0, + 350.0, + 305.0, + 271.0, + 181.0, + 151.0, + 101.0, + 66.0, + 55.0, + 27.0, + 20.0, + 18.0, + 3.0, + 3.0, + 2.0, + 3.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 1.0, + 2.0, + 6.0, + 4.0, + 17.0, + 20.0, + 27.0, + 41.0, + 74.0, + 97.0, + 130.0, + 182.0, + 240.0, + 289.0, + 333.0, + 360.0, + 386.0, + 401.0, + 426.0, + 393.0, + 370.0, + 302.0, + 239.0, + 218.0, + 174.0, + 129.0, + 112.0, + 82.0, + 39.0, + 27.0, + 20.0, + 19.0, + 8.0, + 6.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 3.0, + 9.0, + 19.0, + 24.0, + 56.0, + 62.0, + 100.0, + 121.0, + 159.0, + 207.0, + 269.0, + 312.0, + 326.0, + 316.0, + 396.0, + 385.0, + 331.0, + 323.0, + 280.0, + 241.0, + 189.0, + 158.0, + 126.0, + 86.0, + 67.0, + 33.0, + 22.0, + 10.0, + 6.0, + 4.0, + 2.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + ], + [ + 0.0, + 1.0, + 0.0, + 2.0, + 1.0, + 0.0, + 3.0, + 12.0, + 9.0, + 21.0, + 30.0, + 66.0, + 71.0, + 104.0, + 153.0, + 162.0, + 221.0, + 247.0, + 301.0, + 300.0, + 314.0, + 278.0, + 302.0, + 262.0, + 251.0, + 223.0, + 194.0, + 157.0, + 125.0, + 76.0, + 58.0, + 40.0, + 18.0, + 20.0, + 8.0, + 3.0, + 2.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 1.0, + 5.0, + 1.0, + 4.0, + 1.0, + 9.0, + 19.0, + 24.0, + 50.0, + 76.0, + 88.0, + 102.0, + 138.0, + 199.0, + 195.0, + 211.0, + 218.0, + 245.0, + 273.0, + 236.0, + 221.0, + 203.0, + 161.0, + 152.0, + 105.0, + 92.0, + 69.0, + 49.0, + 24.0, + 10.0, + 11.0, + 5.0, + 6.0, + 4.0, + 1.0, + 1.0, + 1.0, + 0.0, + 0.0, + ], + [ + 0.0, + 1.0, + 0.0, + 1.0, + 2.0, + 1.0, + 6.0, + 6.0, + 16.0, + 16.0, + 19.0, + 42.0, + 35.0, + 68.0, + 104.0, + 115.0, + 139.0, + 176.0, + 175.0, + 231.0, + 226.0, + 210.0, + 205.0, + 180.0, + 155.0, + 136.0, + 108.0, + 89.0, + 70.0, + 57.0, + 32.0, + 22.0, + 11.0, + 8.0, + 5.0, + 4.0, + 1.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 3.0, + 7.0, + 7.0, + 15.0, + 20.0, + 24.0, + 32.0, + 53.0, + 66.0, + 85.0, + 97.0, + 109.0, + 139.0, + 135.0, + 134.0, + 141.0, + 171.0, + 159.0, + 121.0, + 93.0, + 92.0, + 62.0, + 46.0, + 39.0, + 21.0, + 20.0, + 6.0, + 5.0, + 1.0, + 1.0, + 1.0, + 2.0, + 1.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 2.0, + 3.0, + 11.0, + 21.0, + 14.0, + 26.0, + 35.0, + 44.0, + 65.0, + 73.0, + 92.0, + 93.0, + 111.0, + 114.0, + 115.0, + 115.0, + 91.0, + 64.0, + 86.0, + 52.0, + 44.0, + 33.0, + 32.0, + 22.0, + 15.0, + 4.0, + 5.0, + 4.0, + 2.0, + 2.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 4.0, + 4.0, + 12.0, + 16.0, + 31.0, + 30.0, + 28.0, + 59.0, + 50.0, + 51.0, + 62.0, + 77.0, + 66.0, + 77.0, + 61.0, + 75.0, + 49.0, + 63.0, + 41.0, + 34.0, + 23.0, + 17.0, + 21.0, + 8.0, + 9.0, + 4.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 6.0, + 4.0, + 8.0, + 7.0, + 12.0, + 16.0, + 21.0, + 27.0, + 31.0, + 43.0, + 56.0, + 68.0, + 44.0, + 50.0, + 65.0, + 38.0, + 43.0, + 32.0, + 32.0, + 20.0, + 22.0, + 11.0, + 9.0, + 11.0, + 1.0, + 0.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 3.0, + 2.0, + 2.0, + 2.0, + 1.0, + 7.0, + 9.0, + 9.0, + 8.0, + 22.0, + 29.0, + 26.0, + 26.0, + 27.0, + 29.0, + 32.0, + 23.0, + 28.0, + 28.0, + 25.0, + 16.0, + 15.0, + 10.0, + 8.0, + 5.0, + 4.0, + 2.0, + 2.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 2.0, + 2.0, + 4.0, + 6.0, + 11.0, + 13.0, + 9.0, + 17.0, + 11.0, + 27.0, + 23.0, + 25.0, + 19.0, + 24.0, + 21.0, + 15.0, + 22.0, + 12.0, + 7.0, + 1.0, + 4.0, + 2.0, + 2.0, + 2.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 3.0, + 0.0, + 4.0, + 3.0, + 4.0, + 7.0, + 11.0, + 7.0, + 11.0, + 12.0, + 14.0, + 15.0, + 11.0, + 10.0, + 8.0, + 10.0, + 5.0, + 9.0, + 2.0, + 3.0, + 5.0, + 0.0, + 1.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.0, + 8.0, + 4.0, + 5.0, + 4.0, + 6.0, + 11.0, + 3.0, + 8.0, + 8.0, + 4.0, + 6.0, + 4.0, + 0.0, + 1.0, + 7.0, + 1.0, + 3.0, + 2.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.0, + 1.0, + 0.0, + 3.0, + 2.0, + 1.0, + 6.0, + 12.0, + 5.0, + 5.0, + 1.0, + 2.0, + 2.0, + 4.0, + 1.0, + 1.0, + 0.0, + 3.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 2.0, + 2.0, + 0.0, + 0.0, + 6.0, + 3.0, + 2.0, + 2.0, + 1.0, + 1.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 2.0, + 2.0, + 1.0, + 3.0, + 3.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 3.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 0.0, + 0.0, + 2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + ] + + assert xedges.tolist() == [ + -numpy.inf, + -4.0, + -3.8, + -3.6, + -3.4, + -3.2, + -3.0, + -2.8, + -2.5999999999999996, + -2.4, + -2.2, + -2.0, + -1.7999999999999998, + -1.5999999999999996, + -1.4, + -1.1999999999999997, + -1.0, + -0.7999999999999998, + -0.5999999999999996, + -0.3999999999999999, + -0.19999999999999973, + 0.0, + 0.20000000000000018, + 0.40000000000000036, + 0.6000000000000005, + 0.8000000000000007, + 1.0, + 1.2000000000000002, + 1.4000000000000004, + 1.6000000000000005, + 1.8000000000000007, + 2.0, + 2.2, + 2.4000000000000004, + 2.6000000000000005, + 2.8000000000000007, + 3.0, + 3.2, + 3.4000000000000004, + 3.6000000000000005, + 3.8000000000000007, + 4.0, + numpy.inf, + ] + + assert yedges.tolist() == [ + -numpy.inf, + -4.0, + -3.8, + -3.6, + -3.4, + -3.2, + -3.0, + -2.8, + -2.5999999999999996, + -2.4, + -2.2, + -2.0, + -1.7999999999999998, + -1.5999999999999996, + -1.4, + -1.1999999999999997, + -1.0, + -0.7999999999999998, + -0.5999999999999996, + -0.3999999999999999, + -0.19999999999999973, + 0.0, + 0.20000000000000018, + 0.40000000000000036, + 0.6000000000000005, + 0.8000000000000007, + 1.0, + 1.2000000000000002, + 1.4000000000000004, + 1.6000000000000005, + 1.8000000000000007, + 2.0, + 2.2, + 2.4000000000000004, + 2.6000000000000005, + 2.8000000000000007, + 3.0, + 3.2, + 3.4000000000000004, + 3.6000000000000005, + 3.8000000000000007, + 4.0, + numpy.inf, + ] + + f["hpxpy"].values_errors() + + +def test_numpy_profile(): + # python -c 'import ROOT, skhep_testdata; f = ROOT.TFile(skhep_testdata.data_path("uproot-hepdata-example.root")); h = f.Get("hprof"); h.SetErrorOption("g"); print(repr(h.GetErrorOption())); print([h.GetBinError(i) for i in range(102)])' + + with uproot4.open(skhep_testdata.data_path("uproot-hepdata-example.root")) as f: + obj = f["hprof"] + + assert obj.np[1].tolist() == [ + -numpy.inf, + -4.0, + -3.92, + -3.84, + -3.76, + -3.68, + -3.6, + -3.52, + -3.44, + -3.36, + -3.2800000000000002, + -3.2, + -3.12, + -3.04, + -2.96, + -2.88, + -2.8, + -2.7199999999999998, + -2.6399999999999997, + -2.56, + -2.48, + -2.4, + -2.3200000000000003, + -2.24, + -2.16, + -2.08, + -2.0, + -1.92, + -1.8399999999999999, + -1.7599999999999998, + -1.6800000000000002, + -1.6, + -1.52, + -1.44, + -1.3599999999999999, + -1.2799999999999998, + -1.1999999999999997, + -1.12, + -1.04, + -0.96, + -0.8799999999999999, + -0.7999999999999998, + -0.7199999999999998, + -0.6400000000000001, + -0.56, + -0.48, + -0.3999999999999999, + -0.31999999999999984, + -0.23999999999999977, + -0.16000000000000014, + -0.08000000000000007, + 0.0, + 0.08000000000000007, + 0.16000000000000014, + 0.2400000000000002, + 0.3200000000000003, + 0.40000000000000036, + 0.4800000000000004, + 0.5600000000000005, + 0.6399999999999997, + 0.7199999999999998, + 0.7999999999999998, + 0.8799999999999999, + 0.96, + 1.04, + 1.12, + 1.2000000000000002, + 1.2800000000000002, + 1.3600000000000003, + 1.4400000000000004, + 1.5200000000000005, + 1.6000000000000005, + 1.6799999999999997, + 1.7599999999999998, + 1.8399999999999999, + 1.92, + 2.0, + 2.08, + 2.16, + 2.24, + 2.3200000000000003, + 2.4000000000000004, + 2.4800000000000004, + 2.5600000000000005, + 2.6400000000000006, + 2.7199999999999998, + 2.8, + 2.88, + 2.96, + 3.04, + 3.12, + 3.2, + 3.2800000000000002, + 3.3600000000000003, + 3.4400000000000004, + 3.5200000000000005, + 3.6000000000000005, + 3.6799999999999997, + 3.76, + 3.84, + 3.92, + 4.0, + numpy.inf, + ] + + assert obj.np[0][0].tolist() == [ + 17.99833583831787, + 17.05295467376709, + 16.96826426188151, + 15.189482688903809, + 13.73788833618164, + 13.375219821929932, + 13.510369300842285, + 12.646300633748373, + 12.66011929512024, + 11.824836373329163, + 11.623446782430014, + 11.472076733907064, + 10.052986780802408, + 10.030597317603327, + 9.614417321341378, + 8.776622557640076, + 8.620806604623795, + 8.179968640208244, + 7.4127079410317505, + 7.497226472254153, + 6.980819525257234, + 6.505285000189756, + 6.251851732080633, + 5.813575813074431, + 5.584403858840011, + 5.011047506171846, + 4.91228925087014, + 4.524659741255972, + 4.24002511460382, + 4.077462992146468, + 3.638793389923525, + 3.5221418274773493, + 3.255871357954093, + 2.961020285108953, + 2.706199676046999, + 2.5841911697177635, + 2.3627997641933374, + 2.1493446517490598, + 2.0077903614940302, + 1.8382392522714865, + 1.712551970266353, + 1.6131308919867815, + 1.449079261311019, + 1.3471352570103472, + 1.245844892917823, + 1.1707659457058741, + 1.1247396327430272, + 1.1198479739799145, + 1.0281285326813325, + 1.0417602170529079, + 1.0197545518784679, + 1.0003131686022901, + 1.0794705348466953, + 1.02964734215157, + 1.0603044479791786, + 1.1542847645715888, + 1.1745855332784314, + 1.317462644113901, + 1.2909844154549628, + 1.4553258675057892, + 1.5839730073833629, + 1.7274112791524214, + 1.8171250952244693, + 1.999616364569922, + 2.1976474514968105, + 2.332895248766955, + 2.573682461088714, + 2.7457328102556744, + 2.9121971759978718, + 3.157701852473807, + 3.3310595230272195, + 3.685565097902363, + 4.011118740219254, + 4.3144918141177175, + 4.548257073418039, + 4.93563452094951, + 5.191882547210245, + 5.4767660945653915, + 5.7347985672950745, + 6.18110868574559, + 6.4068912520553125, + 7.048662836268797, + 7.238576850891113, + 7.555341683077009, + 8.169158785842185, + 9.019065893613375, + 8.789572896184149, + 9.365243797302247, + 9.570246945256772, + 10.279665088653564, + 11.086111783981323, + 11.118131773812431, + 12.656685405307346, + 12.176475048065186, + 12.393176078796387, + 16.518978118896484, + 13.303139686584473, + 14.635026613871256, + 14.96741771697998, + 0.0, + 18.32199478149414, + 17.8403746287028, + ] + + assert obj.np[0][1].tolist() == [ + 0.2425426377130359, + 0.7421210342302459, + 0.4940066334987832, + 0.0, + 0.0, + 0.2464980351520863, + 0.5555373736396868, + 0.24357921956140027, + 0.224616129931814, + 0.34906168361481404, + 0.4356334723283742, + 0.5128651082538828, + 0.2086307384620165, + 0.28308077003120913, + 0.2891541406820913, + 0.16769727425722117, + 0.1725773236590863, + 0.12765099099147656, + 0.10176558165942572, + 0.15209837443095275, + 0.11509671433352467, + 0.10149120489291587, + 0.11432069747168126, + 0.09759737443630617, + 0.0925726825400381, + 0.06761852807106097, + 0.07883833461255244, + 0.06391971743421765, + 0.07016808339801081, + 0.0679063456384074, + 0.05330254783019173, + 0.056304893803072076, + 0.055238305812566516, + 0.047974962128087315, + 0.042558147198316985, + 0.04422411577185198, + 0.0408986879854767, + 0.03453675368752007, + 0.039438577439864786, + 0.03461426584130604, + 0.036187944978430614, + 0.034085467706933194, + 0.03170797279308202, + 0.031219377450826796, + 0.03011256422687173, + 0.02926608780683337, + 0.0301281364334744, + 0.029773650810830235, + 0.029748389712173053, + 0.03081957669527989, + 0.03132949553456636, + 0.02939420318612115, + 0.029258470846132534, + 0.02930430026995912, + 0.02804401796249436, + 0.031175984988258274, + 0.030108329759273612, + 0.03149116682767534, + 0.029094905772258012, + 0.03256760040302268, + 0.034455467521643364, + 0.03480207320474039, + 0.032712202513451534, + 0.03860859020725239, + 0.03885261043325975, + 0.03856340740992072, + 0.04624045482680718, + 0.04543317885660241, + 0.04864621055120345, + 0.05203738725490573, + 0.043244016740287015, + 0.05850656051444226, + 0.059709748394490884, + 0.06594229969906718, + 0.07220151434675717, + 0.08170131663135467, + 0.08712811029061408, + 0.08092332833341198, + 0.09191356506835095, + 0.10837656197125221, + 0.10509032780349721, + 0.1549338147492931, + 0.12013956272890565, + 0.11435861802671626, + 0.18394299511064918, + 0.36368702093446753, + 0.13346262669376094, + 0.18325723104438668, + 0.17988975869975438, + 0.1926530171606879, + 0.352473088726965, + 0.18420322865597596, + 0.5959353241264886, + 0.21540243485684468, + 0.11755951260322403, + 1.6619844323502102, + 0.1352812684763272, + 0.4534391377411209, + 0.0, + 0.0, + 0.0, + 0.16817919583370047, + ] + + obj._members["fErrorMode"] = uproot4.behaviors.TProfile._kERRORSPREAD + + assert obj.np[0][1].tolist() == [ + 0.34300708770751953, + 1.0495176315307617, + 0.8556445884959498, + 0.0, + 0.0, + 0.3486008644104004, + 1.1110747472793736, + 0.5966447998707816, + 0.7780930984827886, + 0.9872955341457128, + 1.3069004169851226, + 1.9863180231181519, + 0.8080233755703451, + 1.5761270231822468, + 1.7106589658888625, + 1.0606106881094808, + 1.3806185892726903, + 1.0212079279318125, + 0.9158902349348315, + 1.5806526735782713, + 1.281662768690052, + 1.2676247428226026, + 1.4684759475789604, + 1.4109488746385728, + 1.4984197698897908, + 1.1653166117127, + 1.560919388615718, + 1.3285463784181335, + 1.5147207420285738, + 1.549991160077581, + 1.3099853470686935, + 1.443207670599461, + 1.5506131361772943, + 1.4416456163169384, + 1.3979557820249364, + 1.4898998932597651, + 1.39295911912831, + 1.284377246895075, + 1.5059134195962758, + 1.3897530746031688, + 1.5224763480325734, + 1.473186374916331, + 1.367860043067912, + 1.377195694990315, + 1.3845231787179089, + 1.3648794718765778, + 1.4153812430343926, + 1.419488271301224, + 1.419219569870578, + 1.4873439583962957, + 1.5106535672672314, + 1.4343045945107848, + 1.4384340328933711, + 1.4248038889030987, + 1.340257624082002, + 1.4582898146438432, + 1.4006037738107093, + 1.453377907771706, + 1.2814976672937608, + 1.4041886411676958, + 1.446719393622703, + 1.4349262381362273, + 1.3129783240312063, + 1.4747268574003336, + 1.4485303652651937, + 1.3563140181188076, + 1.5217255253773476, + 1.4693963839287074, + 1.449624425594751, + 1.4270014133077806, + 1.1779530457556422, + 1.517791441678946, + 1.406668404280142, + 1.522396207351309, + 1.3812963022723197, + 1.5884551434189818, + 1.4369536067546675, + 1.2947732533345917, + 1.2998541028572388, + 1.429585037043725, + 1.2073959432248138, + 1.6830120202858494, + 1.2013956272890565, + 1.0788570447521093, + 1.705817161574992, + 2.271224717779226, + 0.811821464847988, + 0.9162861552219334, + 0.8627209754934005, + 0.8615704848834633, + 1.40989235490786, + 0.6892253711682418, + 1.787805972379466, + 0.7461759224922005, + 0.3325085142189005, + 2.350400924682617, + 0.1913166046142578, + 1.1106945168733242, + 0.0, + 0.0, + 0.0, + 0.29129491196004526, + ] + + obj._members["fErrorMode"] = uproot4.behaviors.TProfile._kERRORSPREADI + + assert obj.np[0][1].tolist() == [ + 0.2425426377130359, + 0.7421210342302459, + 0.4940066334987832, + 0.2886751345948129, + 0.2886751345948129, + 0.2464980351520863, + 0.5555373736396868, + 0.24357921956140027, + 0.224616129931814, + 0.34906168361481404, + 0.4356334723283742, + 0.5128651082538828, + 0.2086307384620165, + 0.28308077003120913, + 0.2891541406820913, + 0.16769727425722117, + 0.1725773236590863, + 0.12765099099147656, + 0.10176558165942572, + 0.15209837443095275, + 0.11509671433352467, + 0.10149120489291587, + 0.11432069747168126, + 0.09759737443630617, + 0.0925726825400381, + 0.06761852807106097, + 0.07883833461255244, + 0.06391971743421765, + 0.07016808339801081, + 0.0679063456384074, + 0.05330254783019173, + 0.056304893803072076, + 0.055238305812566516, + 0.047974962128087315, + 0.042558147198316985, + 0.04422411577185198, + 0.0408986879854767, + 0.03453675368752007, + 0.039438577439864786, + 0.03461426584130604, + 0.036187944978430614, + 0.034085467706933194, + 0.03170797279308202, + 0.031219377450826796, + 0.03011256422687173, + 0.02926608780683337, + 0.0301281364334744, + 0.029773650810830235, + 0.029748389712173053, + 0.03081957669527989, + 0.03132949553456636, + 0.02939420318612115, + 0.029258470846132534, + 0.02930430026995912, + 0.02804401796249436, + 0.031175984988258274, + 0.030108329759273612, + 0.03149116682767534, + 0.029094905772258012, + 0.03256760040302268, + 0.034455467521643364, + 0.03480207320474039, + 0.032712202513451534, + 0.03860859020725239, + 0.03885261043325975, + 0.03856340740992072, + 0.04624045482680718, + 0.04543317885660241, + 0.04864621055120345, + 0.05203738725490573, + 0.043244016740287015, + 0.05850656051444226, + 0.059709748394490884, + 0.06594229969906718, + 0.07220151434675717, + 0.08170131663135467, + 0.08712811029061408, + 0.08092332833341198, + 0.09191356506835095, + 0.10837656197125221, + 0.10509032780349721, + 0.1549338147492931, + 0.12013956272890565, + 0.11435861802671626, + 0.18394299511064918, + 0.36368702093446753, + 0.13346262669376094, + 0.18325723104438668, + 0.17988975869975438, + 0.1926530171606879, + 0.352473088726965, + 0.18420322865597596, + 0.5959353241264886, + 0.21540243485684468, + 0.11755951260322403, + 1.6619844323502102, + 0.1352812684763272, + 0.4534391377411209, + 0.2886751345948129, + 0.0, + 0.2886751345948129, + 0.16817919583370047, + ] + + obj._members["fErrorMode"] = uproot4.behaviors.TProfile._kERRORSPREADG + + assert obj.np[0][1].tolist() == [ + 0.7071067811865475, + 0.7071067811865475, + 0.5773502691896258, + 1.0, + 1.0, + 0.7071067811865475, + 0.5, + 0.4082482904638631, + 0.2886751345948129, + 0.35355339059327373, + 0.3333333333333333, + 0.2581988897471611, + 0.2581988897471611, + 0.1796053020267749, + 0.1690308509457033, + 0.15811388300841897, + 0.125, + 0.125, + 0.1111111111111111, + 0.09622504486493763, + 0.08980265101338746, + 0.08006407690254357, + 0.0778498944161523, + 0.06917144638660747, + 0.06178020632152154, + 0.058025885318565944, + 0.050507627227610534, + 0.048112522432468816, + 0.04632410546120795, + 0.04381079543383235, + 0.04068942293855797, + 0.03901371573204352, + 0.035623524993954825, + 0.033277916281986085, + 0.03044312827739915, + 0.02968260885977624, + 0.029361010975735173, + 0.026889882837002246, + 0.026189140043946204, + 0.024906774069335894, + 0.023769134427076417, + 0.023137240669137377, + 0.023180714250535184, + 0.022668802672263903, + 0.021749411414517784, + 0.021442250696755896, + 0.021286234067143354, + 0.020974918506045256, + 0.020961090407515925, + 0.020721216851891204, + 0.020739033894608506, + 0.02049369659597791, + 0.020340502363726694, + 0.02056725174474318, + 0.02092434876593436, + 0.02137845624045064, + 0.02149667901961739, + 0.021667569500871973, + 0.022703830459324992, + 0.023193180352135665, + 0.023816275411477048, + 0.024253562503633298, + 0.024914503091731197, + 0.026180163474687157, + 0.026822089039291005, + 0.028432506701809173, + 0.0303868562731382, + 0.030919620705155318, + 0.033557802760701215, + 0.03646624787447364, + 0.036711154910717615, + 0.03854716722458499, + 0.04244763599780089, + 0.043314808182421, + 0.05227083734893167, + 0.05143444998736397, + 0.06063390625908324, + 0.0625, + 0.07071067811865475, + 0.07580980435789034, + 0.08703882797784893, + 0.09205746178983235, + 0.1, + 0.105999788000636, + 0.10783277320343841, + 0.16012815380508713, + 0.1643989873053573, + 0.2, + 0.20851441405707477, + 0.22360679774997896, + 0.25, + 0.2672612419124244, + 0.3333333333333333, + 0.2886751345948129, + 0.35355339059327373, + 0.7071067811865475, + 0.7071067811865475, + 0.4082482904638631, + 1.0, + 0.0, + 1.0, + 0.5773502691896258, + ] + + f["hprof"].values_errors() + + +def test_boost_1d(): + boost_histogram = pytest.importorskip("boost_histogram") + with uproot4.open(skhep_testdata.data_path("uproot-hepdata-example.root")) as f: + f["hpx"].bh + + +def test_boost_2d(): + boost_histogram = pytest.importorskip("boost_histogram") + with uproot4.open(skhep_testdata.data_path("uproot-hepdata-example.root")) as f: + f["hpxpy"].bh + + +def test_boost_profile(): + boost_histogram = pytest.importorskip("boost_histogram") + with uproot4.open(skhep_testdata.data_path("uproot-hepdata-example.root")) as f: + f["hprof"].bh diff --git a/uproot4/__init__.py b/uproot4/__init__.py index 3e3839a0e..1e64b46c3 100644 --- a/uproot4/__init__.py +++ b/uproot4/__init__.py @@ -86,7 +86,7 @@ def behavior_of(classname): globals(), ) module = eval("uproot4.behaviors.{0}".format(name)) - behavior_cls = getattr(module, name) + behavior_cls = getattr(module, name, None) if behavior_cls is not None: globals()[name] = behavior_cls diff --git a/uproot4/behaviors/TH1.py b/uproot4/behaviors/TH1.py new file mode 100644 index 000000000..6c0c3be7c --- /dev/null +++ b/uproot4/behaviors/TH1.py @@ -0,0 +1,129 @@ +# BSD 3-Clause License; see https://github.com/scikit-hep/uproot4/blob/master/LICENSE + +from __future__ import absolute_import + +import numpy + +import uproot4.models.TArray +import uproot4.extras + + +def _edges(axis): + fNbins = axis.member("fNbins") + out = numpy.empty(fNbins + 3, dtype=numpy.float64) + out[0] = -numpy.inf + out[-1] = numpy.inf + + axis_fXbins = axis.member("fXbins", none_if_missing=True) + if axis_fXbins is None or len(axis_fXbins) == 0: + out[1:-1] = numpy.linspace( + axis.member("fXmin"), axis.member("fXmax"), fNbins + 1 + ) + else: + out[1:-1] = axis_fXbins + + return out + + +def _boost_axis(axis): + boost_histogram = uproot4.extras.boost_histogram() + + fNbins = axis.member("fNbins") + fXbins = axis.member("fXbins", none_if_missing=True) + + metadata = axis.all_members + metadata.pop("fXbins", None) + metadata.pop("fLabels", None) + + if axis.member("fLabels") is not None: + return boost_histogram.axis.StrCategory( + [str(x) for x in axis.member("fLabels")], metadata=metadata, + ) + + elif fXbins is None or len(fXbins) == 0: + return boost_histogram.axis.Regular( + fNbins, + axis.member("fXmin"), + axis.member("fXmax"), + underflow=True, + overflow=True, + metadata=metadata, + ) + + else: + return boost_histogram.axis.Variable( + fXbins, underflow=True, overflow=True, metadata=metadata, + ) + + +class TH1(object): + def edges(self, axis=0): + if axis == 0 or axis == "x": + return uproot4.behaviors.TH1._edges(self.member("fXaxis")) + else: + raise ValueError("axis must be 0 or 'x' for a TH1") + + def values(self): + (values,) = self.base(uproot4.models.TArray.Model_TArray) + return numpy.array(values, dtype=values.dtype.newbyteorder("=")) + + def values_errors(self): + # this should work equally well for TH2 and TH3 + + values = self.values() + errors = numpy.zeros(values.shape, dtype=numpy.float64) + + sumw2 = self.member("fSumw2", none_if_missing=True) + if sumw2 is not None and len(sumw2) == self.member("fNcells"): + sumw2 = sumw2.reshape(values.shape) + positive = sumw2 > 0 + errors[positive] = numpy.sqrt(sumw2[positive]) + else: + positive = values > 0 + errors[positive] = numpy.sqrt(values[positive]) + + return values, errors + + @property + def np(self): + return self.values(), self.edges(0) + + @property + def bh(self): + boost_histogram = uproot4.extras.boost_histogram() + + values = self.values() + + sumw2 = self.member("fSumw2", none_if_missing=True) + + if sumw2 is not None and len(sumw2) == self.member("fNcells"): + storage = boost_histogram.storage.Weight() + else: + if issubclass(values.dtype.type, numpy.integer): + storage = boost_histogram.storage.Int64() + else: + storage = boost_histogram.storage.Double() + + xaxis = _boost_axis(self.member("fXaxis")) + out = boost_histogram.Histogram(xaxis, storage=storage) + + metadata = self.all_members + metadata.pop("fXaxis", None) + metadata.pop("fYaxis", None) + metadata.pop("fZaxis", None) + metadata.pop("fContour", None) + metadata.pop("fSumw2", None) + metadata.pop("fBuffer", None) + out.metadata = metadata + + if isinstance(xaxis, boost_histogram.axis.StrCategory): + values = values[1:] + + view = out.view(flow=True) + if sumw2 is not None and len(sumw2) == len(values): + view.value[:] = values + view.variance[:] = sumw2 + else: + view[:] = values + + return out diff --git a/uproot4/behaviors/TH2.py b/uproot4/behaviors/TH2.py new file mode 100644 index 000000000..afce1da08 --- /dev/null +++ b/uproot4/behaviors/TH2.py @@ -0,0 +1,68 @@ +# BSD 3-Clause License; see https://github.com/scikit-hep/uproot4/blob/master/LICENSE + +from __future__ import absolute_import + +import numpy + +import uproot4.models.TArray +import uproot4.behaviors.TH1 + + +class TH2(object): + def edges(self, axis): + if axis == 0 or axis == "x": + return uproot4.behaviors.TH1._edges(self.member("fXaxis")) + elif axis == 1 or axis == "y": + return uproot4.behaviors.TH1._edges(self.member("fYaxis")) + else: + raise ValueError("axis must be 0, 1 or 'x', 'y' for a TH2") + + def values(self): + (values,) = self.base(uproot4.models.TArray.Model_TArray) + values = numpy.array(values, dtype=values.dtype.newbyteorder("=")) + + xaxis_fNbins = self.member("fXaxis").member("fNbins") + yaxis_fNbins = self.member("fYaxis").member("fNbins") + return values.reshape(xaxis_fNbins + 2, yaxis_fNbins + 2) + + # values_errors "inherited" from TH1 + + @property + def np(self): + return self.values(), self.edges(0), self.edges(1) + + @property + def bh(self): + boost_histogram = uproot4.extras.boost_histogram() + + values = self.values() + + sumw2 = self.member("fSumw2", none_if_missing=True) + + if sumw2 is not None and len(sumw2) == self.member("fNcells"): + sumw2 = numpy.array(sumw2, dtype=sumw2.dtype.newbyteorder("=")) + sumw2.reshape(values.shape) + storage = boost_histogram.storage.Weight() + else: + if issubclass(values.dtype.type, numpy.integer): + storage = boost_histogram.storage.Int64() + else: + storage = boost_histogram.storage.Double() + + xaxis = uproot4.behaviors.TH1._boost_axis(self.member("fXaxis")) + yaxis = uproot4.behaviors.TH1._boost_axis(self.member("fYaxis")) + out = boost_histogram.Histogram(xaxis, yaxis, storage=storage) + + if isinstance(xaxis, boost_histogram.axis.StrCategory): + values = values[1:, :] + if isinstance(yaxis, boost_histogram.axis.StrCategory): + values = values[:, 1:] + + view = out.view(flow=True) + if sumw2 is not None and len(sumw2) == len(values): + view.value[:] = values + view.variance[:] = sumw2 + else: + view[:] = values + + return out diff --git a/uproot4/behaviors/TH2Poly.py b/uproot4/behaviors/TH2Poly.py new file mode 100644 index 000000000..3067ab5ca --- /dev/null +++ b/uproot4/behaviors/TH2Poly.py @@ -0,0 +1,22 @@ +# BSD 3-Clause License; see https://github.com/scikit-hep/uproot4/blob/master/LICENSE + +from __future__ import absolute_import + + +class TH2Poly(object): + def edges(self, axis): + raise NotImplementedError(repr(self)) + + def values(self): + raise NotImplementedError(repr(self)) + + def values_errors(self, error_mode=0): + raise NotImplementedError(repr(self)) + + @property + def np(self): + raise NotImplementedError(repr(self)) + + @property + def bh(self): + raise NotImplementedError(repr(self)) diff --git a/uproot4/behaviors/TH3.py b/uproot4/behaviors/TH3.py new file mode 100644 index 000000000..1d63a0e15 --- /dev/null +++ b/uproot4/behaviors/TH3.py @@ -0,0 +1,74 @@ +# BSD 3-Clause License; see https://github.com/scikit-hep/uproot4/blob/master/LICENSE + +from __future__ import absolute_import + +import numpy + +import uproot4.models.TArray +import uproot4.behaviors.TH1 + + +class TH3(object): + def edges(self, axis): + if axis == 0 or axis == "x": + return uproot4.behaviors.TH1._edges(self.member("fXaxis")) + elif axis == 1 or axis == "y": + return uproot4.behaviors.TH1._edges(self.member("fYaxis")) + elif axis == 2 or axis == "z": + return uproot4.behaviors.TH1._edges(self.member("fZaxis")) + else: + raise ValueError("axis must be 0, 1, 2 or 'x', 'y', 'z' for a TH3") + + def values(self): + (values,) = self.base(uproot4.models.TArray.Model_TArray) + values = numpy.array(values, dtype=values.dtype.newbyteorder("=")) + + xaxis_fNbins = self.member("fXaxis").member("fNbins") + yaxis_fNbins = self.member("fYaxis").member("fNbins") + zaxis_fNbins = self.member("fZaxis").member("fNbins") + return values.reshape(xaxis_fNbins + 2, yaxis_fNbins + 2, zaxis_fNbins + 2) + + # values_errors "inherited" from TH1 + + @property + def np(self): + return self.values(), (self.edges(0), self.edges(1), self.edges(2)) + + @property + def bh(self): + boost_histogram = uproot4.extras.boost_histogram() + + values = self.values() + + sumw2 = self.member("fSumw2", none_if_missing=True) + + if sumw2 is not None and len(sumw2) == self.member("fNcells"): + sumw2 = numpy.array(sumw2, dtype=sumw2.dtype.newbyteorder("=")) + sumw2.reshape(values.shape) + storage = boost_histogram.storage.Weight() + else: + if issubclass(values.dtype.type, numpy.integer): + storage = boost_histogram.storage.Int64() + else: + storage = boost_histogram.storage.Double() + + xaxis = uproot4.behaviors.TH1._boost_axis(self.member("fXaxis")) + yaxis = uproot4.behaviors.TH1._boost_axis(self.member("fYaxis")) + zaxis = uproot4.behaviors.TH1._boost_axis(self.member("fZaxis")) + out = boost_histogram.Histogram(xaxis, yaxis, zaxis, storage=storage) + + if isinstance(xaxis, boost_histogram.axis.StrCategory): + values = values[1:, :, :] + if isinstance(yaxis, boost_histogram.axis.StrCategory): + values = values[:, 1:, :] + if isinstance(zaxis, boost_histogram.axis.StrCategory): + values = values[:, :, 1:] + + view = out.view(flow=True) + if sumw2 is not None and len(sumw2) == len(values): + view.value[:] = values + view.variance[:] = sumw2 + else: + view[:] = values + + return out diff --git a/uproot4/behaviors/TProfile.py b/uproot4/behaviors/TProfile.py new file mode 100644 index 000000000..fb1bcde4e --- /dev/null +++ b/uproot4/behaviors/TProfile.py @@ -0,0 +1,162 @@ +# BSD 3-Clause License; see https://github.com/scikit-hep/uproot4/blob/master/LICENSE + +from __future__ import absolute_import + +import numpy + +import uproot4.models.TArray +import uproot4.behaviors.TH1 + + +_kERRORMEAN = 0 +_kERRORSPREAD = 1 +_kERRORSPREADI = 2 +_kERRORSPREADG = 3 + + +class TProfile(object): + def edges(self, axis=0): + if axis == 0 or axis == "x": + return uproot4.behaviors.TH1._edges(self.member("fXaxis")) + else: + raise ValueError("axis must be 0 or 'x' for a TH1") + + def effective_entries(self): + # closely follows the ROOT function, using the same names (with 'root_' prepended) + # https://github.com/root-project/root/blob/ffc7c588ac91aca30e75d356ea971129ee6a836a/hist/hist/src/TProfileHelper.h#L141-L163 + + root_sumOfWeights = self.member("fBinEntries") + root_sumOfWeights = numpy.array(root_sumOfWeights, dtype=numpy.float64) + + root_sumOfWeightSquare = self.member("fBinSumw2") + root_sumOfWeightSquare = numpy.array( + root_sumOfWeightSquare, dtype=numpy.float64 + ) + + if len(root_sumOfWeightSquare) == 0 or len( + root_sumOfWeightSquare + ) != self.member("fNcells"): + return root_sumOfWeights + + positive = root_sumOfWeightSquare > 0 + + out = numpy.zeros(len(root_sumOfWeights), dtype=numpy.float64) + out[positive] = ( + root_sumOfWeights[positive] ** 2 / root_sumOfWeightSquare[positive] + ) + return out + + def values(self): + # duplicates the first part of 'values_errors' + + root_sum = self.member("fBinEntries") + root_sum = numpy.array(root_sum, dtype=numpy.float64) + nonzero = root_sum != 0 + + (root_cont,) = self.base(uproot4.models.TArray.Model_TArray) + root_cont = numpy.array(root_cont, dtype=numpy.float64) + + root_contsum = numpy.zeros(len(root_cont), dtype=numpy.float64) + root_contsum[nonzero] = root_cont[nonzero] / root_sum[nonzero] + + return root_contsum + + def values_errors(self, error_mode=0): + # closely follows the ROOT function, using the same names (with 'root_' prepended) + # https://github.com/root-project/root/blob/ffc7c588ac91aca30e75d356ea971129ee6a836a/hist/hist/src/TProfileHelper.h#L660-L721 + + if error_mode is None or error_mode == _kERRORMEAN or error_mode == "": + error_mode = _kERRORMEAN + elif error_mode == _kERRORSPREAD or error_mode == "s": + error_mode = _kERRORSPREAD + elif error_mode == _kERRORSPREADI or error_mode == "i": + error_mode = _kERRORSPREADI + elif error_mode == _kERRORSPREADG or error_mode == "g": + error_mode = _kERRORSPREADG + else: + raise ValueError( + "error_mode must be None/0/'' for error-on-mean,\n" + " 1/'s' for spread (variance),\n" + " 2/'i' for integer spread (using sqrt(12)),\n" + " or 3/'g' for Gaussian spread\n" + " not " + + repr(error_mode) + + "see https://root.cern.ch/doc/master/classTProfile.html" + ) + + root_sum = self.member("fBinEntries") + root_sum = numpy.array(root_sum, dtype=numpy.float64) + nonzero = root_sum != 0 + + (root_cont,) = self.base(uproot4.models.TArray.Model_TArray) + root_cont = numpy.array(root_cont, dtype=numpy.float64) + + root_contsum = numpy.zeros(len(root_cont), dtype=numpy.float64) + root_contsum[nonzero] = root_cont[nonzero] / root_sum[nonzero] + + if error_mode == _kERRORSPREADG: + out = numpy.zeros(len(root_cont), dtype=numpy.float64) + out[nonzero] = 1.0 / numpy.sqrt(root_sum[nonzero]) + return root_contsum, out + + root_err2 = self.member("fSumw2", none_if_missing=True) + if root_err2 is None or len(root_err2) != self.member("fNcells"): + root_err2 = numpy.zeros(len(root_cont), dtype=numpy.float64) + else: + root_err2 = numpy.array(root_err2, dtype=numpy.float64) + + root_neff = self.effective_entries() + + root_eprim2 = numpy.zeros(len(root_cont), dtype=numpy.float64) + root_eprim2[nonzero] = abs( + root_err2[nonzero] / root_sum[nonzero] - root_contsum[nonzero] ** 2 + ) + root_eprim = numpy.sqrt(root_eprim2) + + if error_mode == _kERRORSPREADI: + numer = numpy.ones(len(root_cont), dtype=numpy.float64) + denom = numpy.full(len(root_cont), numpy.sqrt(12 * root_neff)) + + eprim_nonzero = root_eprim != 0 + numer[eprim_nonzero] = root_eprim[eprim_nonzero] + denom[eprim_nonzero] = numpy.sqrt(root_neff[eprim_nonzero]) + + out = numpy.zeros(len(root_cont), dtype=numpy.float64) + out[nonzero] = numer[nonzero] / denom[nonzero] + return root_contsum, out + + if error_mode == _kERRORSPREAD: + root_eprim[~nonzero] = 0.0 + return root_contsum, root_eprim + + out = numpy.zeros(len(root_cont), dtype=numpy.float64) + out[nonzero] = root_eprim[nonzero] / numpy.sqrt(root_neff[nonzero]) + return root_contsum, out + + @property + def np(self): + return self.values_errors(self.member("fErrorMode")), self.edges(0) + + @property + def bh(self): + boost_histogram = uproot4.extras.boost_histogram() + + storage = boost_histogram.storage.WeightedMean() + + xaxis = uproot4.behaviors.TH1._boost_axis(self.member("fXaxis")) + out = boost_histogram.Histogram(xaxis, storage=storage) + + values, errors = self.values_errors(self.member("fErrorMode")) + + if isinstance(xaxis, boost_histogram.axis.StrCategory): + values = values[1:] + errors = errors[1:] + + view = out.view(flow=True) + + view.sum_of_weights + view.sum_of_weights_squared + view.value = values + view.sum_of_weighted_deltas_squared + + return out diff --git a/uproot4/behaviors/TProfile2D.py b/uproot4/behaviors/TProfile2D.py new file mode 100644 index 000000000..fb2464283 --- /dev/null +++ b/uproot4/behaviors/TProfile2D.py @@ -0,0 +1,29 @@ +# BSD 3-Clause License; see https://github.com/scikit-hep/uproot4/blob/master/LICENSE + +from __future__ import absolute_import + +import uproot4.behaviors.TH1 + + +class TProfile2D(object): + def edges(self, axis): + if axis == 0 or axis == "x": + return uproot4.behaviors.TH1._edges(self.member("fXaxis")) + elif axis == 1 or axis == "y": + return uproot4.behaviors.TH1._edges(self.member("fYaxis")) + else: + raise ValueError("axis must be 0, 1 or 'x', 'y' for a TProfile2D") + + def values(self): + raise NotImplementedError(repr(self)) + + def values_errors(self, error_mode=0): + raise NotImplementedError(repr(self)) + + @property + def np(self): + raise NotImplementedError(repr(self)) + + @property + def bh(self): + raise NotImplementedError(repr(self)) diff --git a/uproot4/behaviors/TProfile3D.py b/uproot4/behaviors/TProfile3D.py new file mode 100644 index 000000000..df95d6243 --- /dev/null +++ b/uproot4/behaviors/TProfile3D.py @@ -0,0 +1,31 @@ +# BSD 3-Clause License; see https://github.com/scikit-hep/uproot4/blob/master/LICENSE + +from __future__ import absolute_import + +import uproot4.behaviors.TH1 + + +class TProfile3D(object): + def edges(self, axis): + if axis == 0 or axis == "x": + return uproot4.behaviors.TH1._edges(self.member("fXaxis")) + elif axis == 1 or axis == "y": + return uproot4.behaviors.TH1._edges(self.member("fYaxis")) + elif axis == 2 or axis == "z": + return uproot4.behaviors.TH1._edges(self.member("fZaxis")) + else: + raise ValueError("axis must be 0, 1, 2 or 'x', 'y', 'z' for a TProfile3D") + + def values(self): + raise NotImplementedError(repr(self)) + + def values_errors(self, error_mode=0): + raise NotImplementedError(repr(self)) + + @property + def np(self): + raise NotImplementedError(repr(self)) + + @property + def bh(self): + raise NotImplementedError(repr(self)) diff --git a/uproot4/compression.py b/uproot4/compression.py index 8bf4866e8..3068c8835 100644 --- a/uproot4/compression.py +++ b/uproot4/compression.py @@ -9,6 +9,7 @@ import uproot4.source.chunk import uproot4.const import uproot4._util +import uproot4.extras class Compression(object): @@ -70,63 +71,25 @@ def decompress(cls, data, uncompressed_bytes=None): class LZMA(Compression): @classmethod def decompress(cls, data, uncompressed_bytes=None): - try: - import lzma - except ImportError: - try: - import backports.lzma as lzma - except ImportError: - raise ImportError( - """install the 'lzma' package with: - - pip install backports.lzma - -or - - conda install backports.lzma - -or use Python >= 3.3.""" - ) + lzma = uproot4.extras.lzma() return lzma.decompress(data) class LZ4(Compression): @classmethod def decompress(cls, data, uncompressed_bytes=None): - try: - import lz4.block - except ImportError: - raise ImportError( - """install the 'lz4' package with (you probably also need 'xxhash'): - - pip install lz4 xxhash - -or - - conda install lz4 python-xxhash""" - ) + lz4_block = uproot4.extras.lz4_block() if uncompressed_bytes is None: raise ValueError( "lz4 block decompression requires the number of uncompressed bytes" ) - return lz4.block.decompress(data, uncompressed_size=uncompressed_bytes) + return lz4_block.decompress(data, uncompressed_size=uncompressed_bytes) class ZSTD(Compression): @classmethod def decompress(cls, data, uncompressed_bytes=None): - try: - import zstandard - except ImportError: - raise ImportError( - """install the 'zstandard' package with: - - pip install zstandard - -or - - conda install zstandard""" - ) + zstandard = uproot4.extras.zstandard() dctx = zstandard.ZstdDecompressor() return dctx.decompress(data) @@ -176,18 +139,8 @@ def decompress(chunk, cursor, context, compressed_bytes, uncompressed_bytes): chunk, _decompress_checksum_format, context ) data = cursor.bytes(chunk, block_compressed_bytes, context) - try: - import xxhash - except ImportError: - raise ImportError( - """install the 'xxhash' package with (you probably also need 'lz4'): - - pip install xxhash lz4 -or - - conda install python-xxhash lz4""" - ) + xxhash = uproot4.extras.xxhash() computed_checksum = xxhash.xxh64(data).intdigest() if computed_checksum != expected_checksum: raise ValueError( diff --git a/uproot4/deserialization.py b/uproot4/deserialization.py index cbf2324d8..ec0040fbb 100644 --- a/uproot4/deserialization.py +++ b/uproot4/deserialization.py @@ -23,6 +23,16 @@ def _actually_compile(class_code, new_scope): exec(compile(class_code, "", "exec"), new_scope) +def _yield_all_behaviors(cls, c): + behavior_cls = uproot4.behavior_of(uproot4.model.classname_decode(cls.__name__)[0]) + if behavior_cls is not None: + yield behavior_cls + if hasattr(cls, "base_names_versions"): + for base_name, base_version in cls.base_names_versions: + for x in _yield_all_behaviors(c(base_name, base_version), c): + yield x + + def compile_class(file, classes, class_code, class_name): new_scope = dict(scope) for cls in classes.values(): @@ -44,9 +54,9 @@ def c(name, version=None): out.class_code = class_code out.__module__ = "" - behavior_cls = uproot4.behavior_of(uproot4.model.classname_decode(class_name)[0]) - if behavior_cls is not None: - out = uproot4._util.new_class(out.__name__, (behavior_cls, out), {}) + behaviors = tuple(_yield_all_behaviors(out, c)) + if len(behaviors) != 0: + out = uproot4._util.new_class(out.__name__, behaviors + (out,), {}) out.__module__ = "" return out diff --git a/uproot4/extras.py b/uproot4/extras.py new file mode 100644 index 000000000..23c1f967f --- /dev/null +++ b/uproot4/extras.py @@ -0,0 +1,211 @@ +# BSD 3-Clause License; see https://github.com/scikit-hep/uproot4/blob/master/LICENSE + +from __future__ import absolute_import + +import os + + +def awkward1(): + try: + import awkward1 + except ImportError: + raise ImportError( + """install the 'awkward1' package with: + + pip install awkward1""" + ) + else: + return awkward1 + + +def pandas(): + try: + import pandas + except ImportError: + raise ImportError( + """install the 'pandas' package with: + + pip install pandas + +or + + conda install pandas""" + ) + else: + return pandas + + +def cupy(): + try: + import cupy + except ImportError: + raise ImportError( + """install the 'cupy' package with: + + pip install cupy + +or + + conda install cupy""" + ) + else: + return cupy + + +def dask_array(): + try: + import dask.array + except ImportError: + raise ImportError( + """install the 'dask.array' package with: + + pip install "dask[array]" + +or + + conda install dask""" + ) + else: + return dask.array + + +def dask_dataframe(): + try: + import dask.dataframe + except ImportError: + raise ImportError( + """install the 'dask.dataframe' package with: + + pip install "dask[dataframe]" + +or + + conda install dask""" + ) + else: + return dask.dataframe + + +def pyxrootd_XRootD_client(): + os.environ["XRD_RUNFORKHANDLER"] = "1" # set multiprocessing flag + try: + import pyxrootd + import pyxrootd.client + import XRootD + import XRootD.client + + except ImportError: + raise ImportError( + """Install pyxrootd package with: + + conda install -c conda-forge xrootd + +(or download from http://xrootd.org/dload.html and manually compile with """ + """cmake; setting PYTHONPATH and LD_LIBRARY_PATH appropriately).""" + ) + + else: + return pyxrootd.client, XRootD.client + + +def lzma(): + try: + import lzma + except ImportError: + try: + import backports.lzma as lzma + except ImportError: + raise ImportError( + """install the 'lzma' package with: + + pip install backports.lzma + +or + + conda install backports.lzma + +or use Python >= 3.3.""" + ) + else: + return lzma + + +def lz4_block(): + try: + import lz4.block + except ImportError: + raise ImportError( + """install the 'lz4' package with (you probably also need 'xxhash'): + + pip install lz4 xxhash + +or + + conda install lz4 python-xxhash""" + ) + else: + return lz4.block + + +def xxhash(): + try: + import xxhash + except ImportError: + raise ImportError( + """install the 'xxhash' package with (you probably also need 'lz4'): + + pip install xxhash lz4 + +or + + conda install python-xxhash lz4""" + ) + else: + return xxhash + + +def zstandard(): + try: + import zstandard + except ImportError: + raise ImportError( + """install the 'zstandard' package with: + + pip install zstandard + +or + + conda install zstandard""" + ) + else: + return zstandard + + +def boost_histogram(): + try: + import boost_histogram + except ImportError: + raise ImportError( + """install the 'boost-histogram' package with: + + pip install boost-histogram + +or + + conda install -c conda-forge boost-histogram""" + ) + else: + return boost_histogram + + +def hist(): + try: + import hist + except ImportError: + raise ImportError( + """install the 'hist' package with: + + pip install hist""" + ) + else: + return hist diff --git a/uproot4/interpretation/library.py b/uproot4/interpretation/library.py index c1cf5606e..4b887d21f 100644 --- a/uproot4/interpretation/library.py +++ b/uproot4/interpretation/library.py @@ -11,6 +11,7 @@ import uproot4.interpretation.strings import uproot4.interpretation.objects import uproot4.containers +import uproot4.extras class Library(object): @@ -257,16 +258,7 @@ class Awkward(Library): @property def imported(self): - try: - import awkward1 - except ImportError: - raise ImportError( - """install the 'awkward1' package with: - - pip install awkward1""" - ) - else: - return awkward1 + return uproot4.extras.awkward1() def finalize(self, array, branch, interpretation, entry_start, entry_stop): awkward1 = self.imported @@ -509,20 +501,7 @@ class Pandas(Library): @property def imported(self): - try: - import pandas - except ImportError: - raise ImportError( - """install the 'pandas' package with: - - pip install pandas - -or - - conda install pandas""" - ) - else: - return pandas + return uproot4.extras.pandas() def finalize(self, array, branch, interpretation, entry_start, entry_stop): pandas = self.imported @@ -816,20 +795,7 @@ class CuPy(Library): @property def imported(self): - try: - import cupy - except ImportError: - raise ImportError( - """install the 'cupy' package with: - - pip install cupy - -or - - conda install cupy""" - ) - else: - return cupy + return uproot4.extras.cupy() def empty(self, shape, dtype): cupy = self.imported @@ -951,20 +917,7 @@ class DaskArray(Library): @property def imported(self): - try: - import dask.array - except ImportError: - raise ImportError( - """install the 'dask.array' package with: - - pip install "dask[array]" - -or - - conda install dask""" - ) - else: - return dask.array + return uproot4.extras.dask_array() def empty(self, shape, dtype): return self.awkward.empty(shape, dtype) @@ -1015,20 +968,7 @@ class DaskFrame(Library): @property def imported(self): - try: - import dask.dataframe - except ImportError: - raise ImportError( - """install the 'dask.dataframe' package with: - - pip install "dask[dataframe]" - -or - - conda install dask""" - ) - else: - return dask.dataframe + return uproot4.extras.dask_dataframe() def empty(self, shape, dtype): return self.awkward.empty(shape, dtype) diff --git a/uproot4/model.py b/uproot4/model.py index 5a2addcda..e9745f9eb 100644 --- a/uproot4/model.py +++ b/uproot4/model.py @@ -235,6 +235,15 @@ def all_members(self): def bases(self): return self._bases + def base(self, *cls): + out = [] + for x in getattr(self, "_bases", []): + if isinstance(x, cls): + out.append(x) + if isinstance(x, Model): + out.extend(x.base(*cls)) + return out + def has_member(self, name, bases=True, recursive_bases=True): if name in self._members: return True diff --git a/uproot4/models/TArray.py b/uproot4/models/TArray.py index d19eaf59b..5e4ad24f5 100644 --- a/uproot4/models/TArray.py +++ b/uproot4/models/TArray.py @@ -26,8 +26,11 @@ def read_members(self, chunk, cursor, context): self._members["fN"] = cursor.field(chunk, _tarray_format1, context) self._data = cursor.array(chunk, self._members["fN"], self.dtype, context) - def __array__(self): - return self._data + def __array__(self, *args, **kwargs): + if len(args) == len(kwargs) == 0: + return self._data + else: + return numpy.array(self._data, *args, **kwargs) @property def nbytes(self): diff --git a/uproot4/models/THashList.py b/uproot4/models/THashList.py index 8dbbbf0cd..e36eacf8e 100644 --- a/uproot4/models/THashList.py +++ b/uproot4/models/THashList.py @@ -6,6 +6,9 @@ class Model_THashList(uproot4.model.Model): + def read_numbytes_version(self, chunk, cursor, context): + pass + def read_members(self, chunk, cursor, context): self._bases.append( uproot4.models.TList.Model_TList.read( diff --git a/uproot4/source/xrootd.py b/uproot4/source/xrootd.py index deeda6e1a..48bdf9aec 100644 --- a/uproot4/source/xrootd.py +++ b/uproot4/source/xrootd.py @@ -6,30 +6,9 @@ from __future__ import absolute_import -import os - import uproot4.source.chunk import uproot4.source.futures - - -def import_pyxrootd(): - os.environ["XRD_RUNFORKHANDLER"] = "1" # set multiprocessing flag - try: - import pyxrootd.client - import XRootD.client - - except ImportError: - raise ImportError( - """Install pyxrootd package with: - - conda install -c conda-forge xrootd - -(or download from http://xrootd.org/dload.html and manually compile with """ - """cmake; setting PYTHONPATH and LD_LIBRARY_PATH appropriately).""" - ) - - else: - return pyxrootd, XRootD +import uproot4.extras def get_server_config(file_path): @@ -45,20 +24,20 @@ def get_server_config(file_path): readv_ior_max (int): The maximum number of bytes that can be requested per **element** in a vector read """ - pyxrootd, XRootD = import_pyxrootd() + pyxrootd_client, XRootD_client = uproot4.extras.pyxrootd_XRootD_client() - url = XRootD.client.URL(file_path) - fs = XRootD.client.FileSystem("{0}://{1}/".format(url.protocol, url.hostid)) + url = XRootD_client.URL(file_path) + fs = XRootD_client.FileSystem("{0}://{1}/".format(url.protocol, url.hostid)) status, readv_iov_max = fs.query( - XRootD.client.flags.QueryCode.CONFIG, "readv_iov_max" + XRootD_client.flags.QueryCode.CONFIG, "readv_iov_max" ) if not status.ok: raise OSError(status.message) readv_iov_max = int(readv_iov_max) status, readv_ior_max = fs.query( - XRootD.client.flags.QueryCode.CONFIG, "readv_ior_max" + XRootD_client.flags.QueryCode.CONFIG, "readv_ior_max" ) if not status.ok: raise OSError(status.message) @@ -81,11 +60,10 @@ def __init__(self, file_path, timeout): timeout (int): Number of seconds (loosely interpreted by XRootD) before giving up on a remote file. """ - - pyxrootd, XRootD = import_pyxrootd() + pyxrootd_client, XRootD_client = uproot4.extras.pyxrootd_XRootD_client() self._file_path = file_path self._timeout = timeout - self._file = pyxrootd.client.File() + self._file = pyxrootd_client.File() status, dummy = self._file.open( self._file_path, timeout=(0 if timeout is None else timeout)