KAHIBARO
Discord Login Register

15.2. Converting Data to ROOT

Creating TTrees

Real experimental data often arrives as plain text, CSV, or some custom format. To analyze it efficiently in ROOT you usually convert it into a TTree inside a ROOT file. A TTree stores data in a columnar structure with branches that correspond to variables, very similar to a table with columns and rows. Each row is one event or one measurement.

To create a TTree from external data you typically follow three steps. First, you decide the structure of the tree: which variables you want to store, their C++ types, and how they group into branches. Second, you create the TTree and its branches in ROOT code. Third, you read the input line by line, assign the values to your variables, and call Fill() so that each input row becomes one TTree entry.

In C++ with ROOT, a minimal example looks like this once you have opened your input file and created output objects:

cpp
TTree *tree = new TTree("data", "Experimental data");
// Variables that will hold values for each entry
Float_t energy;
Int_t   channel;
// Create branches that point to these variables
tree->Branch("energy",  &energy,  "energy/F");
tree->Branch("channel", &channel, "channel/I");
// Inside a loop over lines of a text or CSV file:
while (input >> energy >> channel) {
    tree->Fill();
}

Here energy and channel are ordinary C++ variables. The Branch calls connect each branch name to the memory address of the variable. Every time you update the variables and call Fill(), ROOT copies their current values into a new entry in the tree.

There are two common styles of branch creation. The first uses a type string as shown above, like "energy/F" for a float and "channel/I" for an integer. The second uses the address and lets ROOT infer the type:

cpp
tree->Branch("energy", &energy);
tree->Branch("channel", &channel);

For simple scalar variables both work. For more complex objects such as std::vector<double>, you usually use the second style:

cpp
std::vector<double> times;
tree->Branch("times", &times);

In that case, inside your loop you fill times with values for the current event, then call Fill(), and clear or resize the vector before the next iteration.

When converting an existing dataset, it is useful to keep the mapping between input columns and TTree branches simple and direct. For example, if a CSV file has columns run,event,energy,time, you can create branches with the same names and types. This makes later analysis more transparent and reduces confusion.

A TTree entry is only created when you call Fill(). Updating branch variables without calling Fill() will not store anything. Conversely, calling Fill() with uninitialized or wrong variables will store incorrect data, so always check parsing and assignments before filling the tree.

For absolute beginners, it is often helpful to first print a few input lines in your conversion macro to verify that the parsing works correctly, then print a few entries from the resulting TTree using tree->Scan() in a separate ROOT session. This quick cross check helps you catch common issues such as swapped columns, wrong units, or missing values that were not handled properly during conversion.

Creating ROOT files

A TTree lives inside a ROOT file, typically with the extension .root. The ROOT file acts as a container not only for TTrees but also for histograms, graphs, and any other ROOT objects you want to store. When you convert external data to ROOT, you almost always create a new file, write the TTree into it, and then close the file.

The basic steps are straightforward. First, you create a TFile in write mode. Second, you create the TTree (and any other objects) while this file is open. Third, you write the objects to disk and close the file. In a typical conversion macro this might look like:

cpp
void convert_text_to_root() {
    // Open input text or CSV file using standard C++
    std::ifstream in("data.csv");
    if (!in.is_open()) {
        std::cerr << "Cannot open input file\n";
        return;
    }
    // Create ROOT output file
    TFile *outfile = new TFile("data.root", "RECREATE");
    // Create TTree and branches
    TTree *tree = new TTree("data", "Converted experimental data");
    Float_t energy;
    Int_t   channel;
    tree->Branch("energy",  &energy,  "energy/F");
    tree->Branch("channel", &channel, "channel/I");
    // Read input and fill tree
    while (in >> energy >> channel) {
        tree->Fill();
    }
    // Write everything to the ROOT file
    outfile->Write();
    outfile->Close();
}

The file mode "RECREATE" creates a new file or overwrites an existing one with the same name. You can also use "CREATE" if you want ROOT to fail instead of overwrite when the file already exists, or "UPDATE" if you want to add new objects to an existing file without deleting the old ones.

Once you have called Write() and closed the file, you can open it in a separate ROOT session and inspect its contents:

cpp
TFile *f = TFile::Open("data.root");
f->ls();          // list the objects in the file
TTree *t = (TTree*)f->Get("data");
t->Print();       // display the tree structure

If your conversion script creates several TTrees in the same file, or combines TTrees with histograms, you do not need to call Write() for each object individually when using "RECREATE" or "UPDATE". Calling outfile->Write() at the end will by default write all objects that are associated with the file. For very large files or more advanced projects you might choose to write and flush objects periodically, but for beginners a single Write() at the end is usually sufficient.

Always close your ROOT file after writing, using file->Close(). If you forget to close the file, some data may not be written correctly, which can lead to incomplete or corrupted files. Closing the file also ensures that all internal buffers are flushed and the file can be safely opened in later ROOT sessions.

To summarize the conversion in one flow, you read external data with standard C++ or another library, create a ROOT file and a TTree with suitable branches, loop over your events or rows, assign values to branch variables, call Fill() for each event, then write and close the file. Once this is done, your real experimental data is ready for efficient analysis in ROOT using TTrees and all the tools introduced in later chapters.

Views: 13

Comments

Please login to add a comment.

Don't have an account? Register now!