uproot: Python/NumpyベースでROOTファイルを読み書きするライブラリ

# 概要

CERN開発の解析ライブラリROOT(C++ベース)では、データを\*.rootファイルで管理・保存します。
uprootはこの\*.rootファイルを高速に読み書きするpython/numpyベースのライブラリです。
リリースされて比較的日が浅いこともあり日本語のドキュメントが見当たらないですが、素粒子・原子核・宇宙実験を中心に使用されるROOTとpythonベース機械学習フレームワークをつなぐ上で非常に有用そうなので、まとめてみました。
[公式ドキュメント](https://github.com/scikit-hep/uproot)
[ROOT ホームページ](https://root.cern.ch/)
![logo-uproot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277751/dc277c76-513b-352b-6d29-960016b46e10.png)

# 背景
作成したROOTファイルを元に機械学習を走らせたい場合、いくつかの方法が考えられます。
1. ROOTマクロでcsv形式などにして読み出す
2. pyROOTなどを使用して読み出す

1はcsv形式に変換するROOTマクロとcsvを読み出してnumpy arrayなどに変換するpythonコードの両方を書く必要があり、明らかに面倒です。
一方で、一見straightforwardに見える2は、Google Colabなどのクラウド環境で走らせる場合、pyROOTをしっかり導入するのはかなり大変そうです。(一応可能のようですが...[リンク](https://stackoverflow.com/questions/55544013/installing-cern-root-on-google-jupyter-notebook))

そんな中、最近リリースされたuprootは、ROOTファイルをそのまま扱えて、かつ導入コストが高くないuprootは非常に助かる存在になると思います。さらに、膨大なデータ量も高速に読み出せるという機械学習に使用する上で非常に重要な特徴をも備えています。開発チームによれば、大きなサイズのファイルに対しては本家のROOTをも上回る読み出しスピードを達成するそうです。
![root-none-muon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277751/e2b9720b-59f5-2c17-cdf7-618b01b3d0dd.png)

すでに[LHC(Large Hadron Collider)実験](https://home.cern/science/accelerators/large-hadron-collider)や、[XENON-nT実験](https://science.purdue.edu/xenon1t/)といった(界隈では)メジャーな実験に多くのユーザーがいるようで、今後も継続的に開発が行われそうです。また、[LHCで開発されているMLaaS](https://arxiv.org/abs/1811.04492)にも使用されています。

![uproot_popularity.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277751/1bbe7f6e-5a0a-93eb-b8fa-296c16855e98.png)


# インストール
pipコマンドでインストールできます。

```shell
pip install uproot
```

condaでもインストールすることができます。

```shell
conda config --add channels conda-forge  # if you haven't added conda-forge already
conda install uproot
```

C++ ROOTがインストールされていることはuprootを使用する上で必要ありません。

# 使い方

本稿では、最も基本的な使い道のみに絞ってご紹介します。uprootでは、ROOTオブジェクトの名前を使って読み込んだり書き込んだりします。

## 適当なROOTファイルを作る
適当なROOTマクロです。実行は`root -l GenRootFile.cpp`.

```c++:GenRootFile.cpp

void GenRootFile(){
   TFile* fout = new TFile("sample.root","recreate");
   TTree* tout = new TTree("tout","tout");
   Int_t    event;
   Double_t val;
   tout->Branch("event",&event,"event/I");
   tout->Branch("val"  ,&val  ,"val/D");
   
   TF1* fgaus = new TF1("fgaus","[0]*TMath::Gaus(x,[1],[2])",-10,10);
   fgaus->SetParameters(1,0,2);
   TH1D* hgaus = new TH1D("hgaus","hgaus",20,-10,10);
   Int_t Nevent = 100;
   for (Int_t ievent = 0; ievent < Nevent; ievent++) {
      event = ievent;
      val   = fgaus->GetRandom();
      
      hgaus ->Fill(val);
      tout  ->Fill();
   }
   
   fout ->cd();
   tout ->Write();
   hgaus->Write();
   fout ->Close();
}
```

例えばこんなヒストグラムが生成されます。

![histogram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277751/75b500d6-a9b6-e182-3590-f95a8ff79aa7.png)


## rootファイルのtreeを読み込む
Treeの持っている名前(今回なら"tout")でアクセスします。

```python
import uproot
file = uproot.open("sample.root")
tout = file["tout"] #TName
print(tout)
```

## Treeの要素にアクセスする
TreeのBranchをnumpy arrayとして読み込むことができます。

```python

val  = tout.array("val")
```

## Histogramを読み込む
Histogramも読み込むことができる。

```python
hgaus = file["hgaus"] # TNameで取得
print(hgaus.edges) # ヒストグラムのx軸
print(hgaus.values)  # ヒストグラムのy軸(値)
hgaus.show()
```

実行結果はこんな感じ。

```
[-10.  -9.  -8.  -7.  -6.  -5.  -4.  -3.  -2.  -1.   0.   1.   2.   3.
   4.   5.   6.   7.   8.   9.  10.]
[ 0.  0.  0.  0.  0.  0.  5. 13. 17. 24. 20. 14.  2.  3.  1.  0.  1.  0.
  0.  0.]
               0                                                            25.2
               +---------------------------------------------------------------+
[-inf, -10) 0  |                                                               |
[-10, -9)   0  |                                                               |
[-9, -8)    0  |                                                               |
[-8, -7)    0  |                                                               |
[-7, -6)    0  |                                                               |
[-6, -5)    0  |                                                               |
[-5, -4)    0  |                                                               |
[-4, -3)    5  |************                                                   |
[-3, -2)    13 |********************************                               |
[-2, -1)    17 |******************************************                     |
[-1, 0)     24 |************************************************************   |
[0, 1)      20 |**************************************************             |
[1, 2)      14 |***********************************                            |
[2, 3)      2  |*****                                                          |
[3, 4)      3  |*******                                                        |
[4, 5)      1  |**                                                             |
[5, 6)      0  |                                                               |
[6, 7)      1  |**                                                             |
[7, 8)      0  |                                                               |
[8, 9)      0  |                                                               |
[9, 10)     0  |                                                               |
[10, inf]   0  |                                                               |
               +---------------------------------------------------------------+
```

## 書き込み
次のように、新しいROOTファイルを作成して、ヒストグラムや新しいTreeを書き込むこともできます。

```python
import numpy as np
t = uproot.newtree({"branch1": int,
                    "branch2": np.int32,
                    "branch3": uproot.newbranch(np.float64, title="This is the title")})
with uproot.recreate("example.root") as f:
    f["hist"] = hgaus #オブジェクトの名前を適当につける
    f["t"] = t
```

# まとめ
uprootを使えばROOTファイルをpython環境で(高速で)読み書きできることがわかりました。python系の機械学習フレームワーク(PyTorch、Tensorflowなど)で大きなファイルを食わせたい場合には特に有用そうです。