If you really feel lost without ROOT¶

You can import ROOT in python¶

If you setted correctly also the PYTHONPATH environment variable (the thisroot.sh script should)

In [1]:
!echo $PYTHONPATH
/Users/carlo/soft/root-6.26.02-install/lib:/usr/local/lib/python:
In [2]:
import sys
for s in sys.path:
    if "root" in s:
        print(s)
/Users/carlo/soft/root-6.26.02-install/lib

And then you can use ROOT in python¶

In [3]:
import ROOT
Welcome to JupyROOT 6.26/02

Essentially in the same way as in C++

In [4]:
h = ROOT.TH1F("h2","h2",100,-20,20)
In [5]:
h.FillRandom('gaus',1000)
In [6]:
c = ROOT.TCanvas("c","c",800,600)
In [7]:
h.Draw()

You have to Draw the TCanvas to see something...

In [8]:
c.Draw()

You can also use an interactive viewer (which is not working in the slideshow mode)

In [9]:
ROOT.enableJSVis()
In [10]:
c.Draw()
In [11]:
ROOT.disableJSVis()

Pay attention!!¶

Old versions of ROOT do not throw an exception if there is an error in reading the file

But just a message

In [12]:
file = ROOT.TFile('nonexisting.root','READ')
---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
<ipython-input-12-c9cacff7c5f9> in <module>
----> 1 file = ROOT.TFile('nonexisting.root','READ')

~/soft/root-6.26.02-install/lib/ROOT/_pythonization/_tfile.py in _TFileConstructor(self, *args)
     53     if len(args) >= 1:
     54         if self.IsZombie():
---> 55             raise OSError('Failed to open file {}'.format(args[0]))
     56 
     57 def _TFileOpen(klass, *args):

OSError: Failed to open file nonexisting.root
Error in <TFile::TFile>: file /Users/carlo/repos/pycourse/Slides/nonexisting.root does not exist

then if you read the file in a verbose script you may not see the error and it can keep running...

Let me suggest you to wrap the ROOT function to raise an exception in case the file reading has problems:

In [13]:
def my_read_rootfile(filename):
    inputfile = ROOT.TFile(filename,'READ')
    if not inputfile.IsOpen():
        raise FileNotFoundError("file",filename,"not found")
    if inputfile.IsZombie():
        raise OSError("error opening the file",filename)
        
my_read_rootfile('nonexisting.root')
---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
<ipython-input-13-0aa63365e13d> in <module>
      6         raise OSError("error opening the file",filename)
      7 
----> 8 my_read_rootfile('nonexisting.root')

<ipython-input-13-0aa63365e13d> in my_read_rootfile(filename)
      1 def my_read_rootfile(filename):
----> 2     inputfile = ROOT.TFile(filename,'READ')
      3     if not inputfile.IsOpen():
      4         raise FileNotFoundError("file",filename,"not found")
      5     if inputfile.IsZombie():

~/soft/root-6.26.02-install/lib/ROOT/_pythonization/_tfile.py in _TFileConstructor(self, *args)
     53     if len(args) >= 1:
     54         if self.IsZombie():
---> 55             raise OSError('Failed to open file {}'.format(args[0]))
     56 
     57 def _TFileOpen(klass, *args):

OSError: Failed to open file nonexisting.root
Error in <TFile::TFile>: file /Users/carlo/repos/pycourse/Slides/nonexisting.root does not exist

uproot¶

A good alternative to installing ROOT is uproot

  • a library for reading and writing ROOT files in pure Python and numpy
  • part of the Scikit-HEP project, a collection of Python tools for HEP data analysis
  • can work WITHOUT ROOT installed

Let's have a look on how to use it on the output of the Geant4 AnaEx01 extended example

In [14]:
import uproot

you can open a file as:

In [15]:
file = uproot.open('AnaEx01.root')

and check the list of keys in the file:

In [16]:
file.keys()
Out[16]:
['histo;1',
 'histo/EAbs;1',
 'histo/EGap;1',
 'histo/LAbs;1',
 'histo/LGap;1',
 'ntuple;1',
 'ntuple/Ntuple1;1',
 'ntuple/Ntuple2;1']

Get one of the TTrees

In [17]:
tree = file['ntuple/Ntuple1']

check the keys of that TTree

In [18]:
tree.keys()
Out[18]:
['Eabs', 'Egap']

and get the branches

In [19]:
branches = tree.arrays()
In [20]:
branches['Egap']
Out[20]:
<Array [21.4, 16.7, 21.5, ... 20.3, 14.1, 17.4] type='1000 * float64'>
In [21]:
type(branches['Egap'])
Out[21]:
awkward.highlevel.Array

finally you can use them (almost) as numpy arrays

for instance, you can do an histogram

In [22]:
import matplotlib.pyplot as plt
In [23]:
plt.hist(branches['Egap'])
Out[23]:
(array([  1.,  32., 136., 259., 247., 177.,  92.,  44.,   9.,   3.]),
 array([ 2.71100092,  7.56506954, 12.41913817, 17.27320679, 22.12727542,
        26.98134405, 31.83541267, 36.6894813 , 41.54354992, 46.39761855,
        51.25168717]),
 <BarContainer object of 10 artists>)

and make analysis

In [24]:
plt.hist(branches['Egap'])
plt.hist(branches['Egap'][branches['Eabs']<450])
Out[24]:
(array([ 1.,  5., 22., 48., 79., 70., 59., 35.,  9.,  3.]),
 array([ 2.71100092,  7.56506954, 12.41913817, 17.27320679, 22.12727542,
        26.98134405, 31.83541267, 36.6894813 , 41.54354992, 46.39761855,
        51.25168717]),
 <BarContainer object of 10 artists>)

you may have some problem with the fact that the output is not a numpy array

In [25]:
plt.hist2d(branches['Egap'],branches['Egap'])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-25-c594ef3e6f4d> in <module>
----> 1 plt.hist2d(branches['Egap'],branches['Egap'])

/usr/local/lib/python3.9/site-packages/matplotlib/pyplot.py in hist2d(x, y, bins, range, density, weights, cmin, cmax, data, **kwargs)
   2696         x, y, bins=10, range=None, density=False, weights=None,
   2697         cmin=None, cmax=None, *, data=None, **kwargs):
-> 2698     __ret = gca().hist2d(
   2699         x, y, bins=bins, range=range, density=density,
   2700         weights=weights, cmin=cmin, cmax=cmax,

/usr/local/lib/python3.9/site-packages/matplotlib/__init__.py in inner(ax, data, *args, **kwargs)
   1436     def inner(ax, *args, data=None, **kwargs):
   1437         if data is None:
-> 1438             return func(ax, *map(sanitize_sequence, args), **kwargs)
   1439 
   1440         bound = new_sig.bind(ax, *args, **kwargs)

/usr/local/lib/python3.9/site-packages/matplotlib/axes/_axes.py in hist2d(self, x, y, bins, range, density, weights, cmin, cmax, **kwargs)
   6926         """
   6927 
-> 6928         h, xedges, yedges = np.histogram2d(x, y, bins=bins, range=range,
   6929                                            density=density, weights=weights)
   6930 

<__array_function__ internals> in histogram2d(*args, **kwargs)

TypeError: no implementation found for 'numpy.histogram2d' on types that implement __array_function__: [<class 'awkward.highlevel.Array'>]

ROOT data can be not rectangular

numpy is designed to work with rectangular arrays

In [26]:
#rectangular array
[[0, 1],
 [2, 3],
 [4, 5],
 [6, 7],
 [8, 9]]
Out[26]:
[[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]
In [27]:
#jagged array
[[0, 1],
 [2, 3, 4],
 [5],
 [6, 7],
 [8, 9]]
Out[27]:
[[0, 1], [2, 3, 4], [5], [6, 7], [8, 9]]

Awkward Array¶

a package used to deal with jagged arrays

methods pretty similar to numpy

In [28]:
import awkward as ak

if the data are numerical and regular can be losslessly converted to a numpy array

In [31]:
plt.hist2d(ak.to_numpy(branches['Egap']),ak.to_numpy(branches['Egap']));
plt.show
Out[31]:
<function matplotlib.pyplot.show(close=None, block=None)>
In [ ]: