Hello,
The modern way to treat your problem is through RDataFrame. It will also be valid once ROOT moves to its new columnar storage, RNTuple.
I provide below an example to accomplish what I think I understood you want to do: please correct me if I am wrong!
Please note the compactness and expressiveness of RDataFrame: this means less mistakes, less maintenance, less headaches…
To run the example root test.C
.
You have 4 functions in the code:
- test(): nothing, it just invokes the other 3

- write(): I tried to emulate your root file, I hope it’s not too far from what you have
- read_v1(): easy, string based but slightly underperformant version of RDataFrame
- read_v2(): fully compiled, highly performant version of what you are trying to accomplish.
I took the freedom to plot all the quantities as well.
Let us know if you have further questions!
Cheers,
Danilo
void write(){
// create a dummy dataset for this example
// The types of the columns and the values are clearly made up!
auto getValues = [](int nParticles) {
std::vector<float> energy(nParticles);
std::generate_n(energy.begin(), nParticles, [&](){return gRandom->Uniform(0., 100.);});
return energy;
};
auto df = ROOT::RDataFrame(1024);
df.Define("idEvent", []() { return int(gRandom->Uniform(0.5, 8.5)); })
.Define("nParticles", []() { return int(gRandom->Uniform(0.5, 256.5)); })
.Define("energy", getValues, {"nParticles"})
.Define("time", getValues, {"nParticles"})
.Snapshot("myTree", "myFile.root");
}
void read_v1(){
// Extended version, using strings
auto df = ROOT::RDataFrame("myTree","myFile.root");
auto df0 = df.Filter("idEvent == 0");
auto h_time_0 = df0.Histo1D("time");
auto h_energy_0 = df0.Histo1D("energy");
auto df1 = df.Filter("idEvent == 1");
auto h_time_1 = df1.Histo1D("time");
auto h_energy_1 = df1.Histo1D("energy");
// ... and so on.
for (auto &&h : {&h_time_0, &h_time_1, &h_energy_0, &h_energy_1}) {
new TCanvas(); // this stays after the function returns...
(*h)->DrawClone();
}
}
void read_v2(){
// Compact, more performant version with C++ callables
class idFilter {
private:
int fId;
public:
idFilter(int id) : fId(id) {}
bool operator()(int id) { return id == fId; }
};
auto df = ROOT::RDataFrame("myTree", "myFile.root");
auto df0 = df.Filter(idFilter(0), {"idEvent"});
auto h_time_0 = df0.Histo1D<std::vector<float>>({"ht0","time idEvent = 0",64,0,100}, "time");
auto h_energy_0 = df0.Histo1D<std::vector<float>>({"he0","energy idEvent = 0",64,0,100}, "energy");
auto df1 = df.Filter(idFilter(1), {"idEvent"});
auto h_time_1 = df1.Histo1D<std::vector<float>>({"ht1","time idEvent = 1",64,0,100}, "time");
auto h_energy_1 = df1.Histo1D<std::vector<float>>({"he1","energy idEvent = 1",64,0,100}, "energy");
// ... and so on.
for (auto &&h : {&h_time_0, &h_time_1, &h_energy_0, &h_energy_1}) {
new TCanvas(); // this stays after the function returns...
(*h)->DrawClone();
}
}
void test(){
write();
read_v1();
read_v2();
}