Keita Mizukoshi’s page
Keita Mizukoshi’s page

TTree / RDataFrame

本記事ではROOTでのデータ保存形式であるTTreeを, ROOTの機能であるRDataFrameを使って利用する方法を解説します.

TTree class referecnce

RDataFrame class reference

上記に全て書いてありますが, 一応よく使いそうなケースを示します.

  • TTreeを読む
  • ROOTファイルの中身の確認
  • RDataFrameの構築
  • これまで必要だった記述
  • Treeのお絵描き
  • 新しいcolumn (branch)の定義
  • Treeの保存
  • 雑感

TTreeを読む

ROOTファイルの中身の確認

今, 中身をよく知らないrootファイルがあったとします. 実験をやっていると誰かの作ったrootファイルがあって, 中身についてのDocumentは全然ないということがよくあります. ここでは huge.root というファイルがあったとします.

最近のROOTには rootls というコマンドが同梱されています. まずこれで中身を確認します.

TNamedとか, TParameterなども入っていますが, ここではTTreeに着目します.

tree という名前のTTreeが入っていることがわかり, その中に nHit や x などの変数 (branch) が入っていることがわかります. このファイルが欲しかったBranchがあれば, 次に進みます. そうでなければ, ROOTファイル探しの旅を続けることになります.

これまでは, root -l huge.root として, ROOTのプロンプトで .ls する方法が主流でした. 好みの範囲に収まる程度の違いだと思います.

RDataFrameの構築

root と打ってROOTのPromptを起動して, RDataFrameを作ります.

ROOT::EnableImplicitMT(); // For multi-thread processing
ROOT::RDataFrame df ("tree", "huge.root")

rootマクロとしてではなく, C++としてコンパイルする場合は#include <ROOT/RDataFrame.hxx> が必要です.

df.Describe() 関数で中身を調べられます.

入っている変数の型もここでわかります.

💡
TTreeを読みたい場合はこれまで結構大変な記述が必要でしたがtree->SetBranchAddress() とか今や必要ありません.
‣

これまで必要だった記述

df.GetColumnNames() でBranch名 (RDataFrameの流儀ではColumn) をリストで受け取ることができるので,

for (auto c : df.GetColumnNames()){
	// Something to do
	std::cout << c << std::endl;
}

こんな感じでBranch全てに対して行う処理を実行できます.

Treeのお絵描き

まずはヒストグラム

auto h = df.Histo1D("nHit");
h->Draw();

これまで, tree->Draw("nHit") としていたものの代わりになります.

Cutをかけたければ,

auto h1 = df.Filter("nHit>0").Histo1D("eDep") とかでFilterしてください.

tree->Draw() の第二引数にCutを書くより, Filterの結果が使いまわされることに優位性があります.

たくさんTreeを作った場合も, 使う時 (つまり, ポインタにアクセスする際に作成が始まり, まとめられるときは一つのloopで行われます)

ヒストグラムをたくさん定義して,

auto h_x = df.Histo1D("x");
auto h_y = df.Histo1D("y");
auto h_z = df.Histo1D("z");
auto h_eDep = df.Histo1D("eDep");
auto h_eIn = df.Histo1D("eIn");

ヒストグラムを描画します.

h_x->Draw();

この描画しようとした瞬間にevent loopが走ります. 同じevent loopで h_y なども作られるため, 他のヒストグラムを書くのは爆速です.

df.Describe()のEvent loops runでloop回数を確認できます. この場合は5個ヒストグラムを作っていますが, event loopは1しか増えていないことが確認できます.

新しいcolumn (branch)の定義

df.Define() で追加します.

auto df_new = df.Define("new_column", "nHit*2")

nHit*2 の部分はラムダ式や関数を直接入れることが可能.

その場合は第三引数に値のリストを渡す必要がある. {"val1", "val2"} など

公式のExample

// assuming a function with signature:
double myComplexCalculation(const RVec<float> &muon_pts);
// we can pass it directly to Define
auto df_with_define = df.Define("newColumn", myComplexCalculation, {"muon_pts"});
// alternatively, we can pass the body of the function as a string, as in Filter:
auto df_with_define = df.Define("newColumn", "x*x + y*y");

Treeの保存

これまでの tree->Fill() , tree->Write(), file->Close() を完全に過去にしています.

df_new.Snapshot("new_tree", "new_file.root",
                    {"new_column", "x", "y", "z"});

これでできてしまいます.

雑感

  • 宣言的プログラミングっぽさがあります.
  • ROOTはついに “ユーザーは愚かだからLoopを書かせない” という進化をしてきているのかもしれません.
  • 煩雑なTTreeの手続きがかなり楽になっています. Branchに文字列でアクセスすることができるだけでかなり幸せです.
  • 真面目に使おうとするとラムダ式とauto盛り盛りになるので, C++11に追いついてきていないユーザーには意味不明なコードになるでしょう.
  • printf デバッグをしてきた人は大変に, Unitテストを書く側からするとより楽になる気がします.
Home

Keita Mizukoshi

> rootls -lt huge.root
TNamed            Aug 11 13:39 2022 G4Version;1   " Geant4 version Name: geant4-11-00-patch-02 [MT]   (25-May-2022)"
TNamed            Aug 11 13:39 2022 git_date;1    "Thu Aug 11 12:18:29 2022"
TNamed            Aug 11 13:39 2022 git_sha1;1    "c6c933558eb5a49534b4ee8706f2c9d6df6189c6"
TNamed            Aug 11 13:39 2022 git_subject;1 "Add tentative progress"
TNamed            Aug 11 13:39 2022 ROOTVersion;1 "6.26/04"
TParameter<long>  Aug 11 13:39 2022 seed;1        ""
TTree             Aug 11 13:40 2022 tree;10       "mc output" [current cycle]
  nHit      "nHit/I"    40012851
  x         "x"         212785592
  y         "y"         212785592
  z         "z"         212785592
  time      "time"      212788101
  eIn       "eIn"       212787290
  eDep      "eDep"      212788101
  TrackID   "TrackID"   176419006
  copyNo    "copyNo"    176418311
  particle  "particle"  176419701
  Cluster INCLUSIVE ranges:
   - # 0: [0, 1011856]
   - # 1: [1011857, 2023713]
   - # 2: [2023714, 3035570]
   - # 3: [3035571, 4047427]
   - # 4: [4047428, 5059284]
   - # 5: [5059285, 6071141]
   - # 6: [6071142, 7082998]
   - # 7: [7082999, 8094855]
   - # 8: [8094856, 9106712]
   - # 9: [9106713, 9999999]
  The total number of clusters is 10
TTree             Aug 11 13:40 2022 tree;9        "mc output" [backup cycle]
  nHit      "nHit/I"    36439277
  x         "x"         193782650
  y         "y"         193782650
  z         "z"         193782650
  time      "time"      193785156
  eIn       "eIn"       193784346
  eDep      "eDep"      193785156
  TrackID   "TrackID"   160664482
  copyNo    "copyNo"    160663788
  particle  "particle"  160665176
  Cluster INCLUSIVE ranges:
   - # 0: [0, 1011856]
   - # 1: [1011857, 2023713]
   - # 2: [2023714, 3035570]
   - # 3: [3035571, 4047427]
   - # 4: [4047428, 5059284]
   - # 5: [5059285, 6071141]
   - # 6: [6071142, 7082998]
   - # 7: [7082999, 8094855]
   - # 8: [8094856, 9106712]
  The total number of clusters is 9
df.Describe()
(ROOT::RDF::RDFDescription) Dataframe from TChain tree in file huge.root

Property                Value
--------                -----
Columns in total           10
Columns from defines        0
Event loops run             0
Processing slots            1

Column          Type                            Origin
------          ----                            ------
TrackID         ROOT::VecOps::RVec<int>         Dataset
copyNo          ROOT::VecOps::RVec<int>         Dataset
eDep            ROOT::VecOps::RVec<double>      Dataset
eIn             ROOT::VecOps::RVec<double>      Dataset
nHit            Int_t                           Dataset
particle        ROOT::VecOps::RVec<int>         Dataset
time            ROOT::VecOps::RVec<double>      Dataset
x               ROOT::VecOps::RVec<double>      Dataset
y               ROOT::VecOps::RVec<double>      Dataset
z               ROOT::VecOps::RVec<double>      Dataset