18.6 Statistical Operations
Table of Contents
Count
RDataFrame provides ready made statistical actions that you can attach at the end of your analysis graph. The simplest one is Count, which tells you how many entries pass all filters that precede it.
You typically start from a ROOT::RDataFrame built from a TTree or a range, apply some Filter and Define calls, and then attach Count:
ROOT::RDataFrame df("Events", "data.root");
auto df_selected = df.Filter("energy > 1.0", "Energy cut");
// Create a lazy action
auto nSelected = df_selected.Count();
// Trigger the event loop and get the result
std::cout << "Selected events: " << *nSelected << std::endl;
Count returns a smart pointer to an unsigned long long value. The actual computation only happens when you dereference the result with *nSelected or call .GetValue(). This is the same lazy execution model used by all RDataFrame actions. If you define several actions, RDataFrame runs a single event loop and fills all of them at once.
Count always counts entries of the current RDataFrame, not the original one. If you apply several filters in sequence, Count only sees events that survived all previous steps. This makes it ideal for quickly checking how many events pass a given selection, for example to compute efficiencies or to debug cuts.
You can create several Count actions on different filtered dataframes:
auto nTotal = df.Count();
auto nPassCut1 = df.Filter("pt > 0.5").Count();
auto nPassCut2 = df.Filter("pt > 0.5 && eta < 2.5").Count();
std::cout << "Total: " << *nTotal
<< ", pt>0.5: " << *nPassCut1
<< ", pt>0.5 && |eta|<2.5: " << *nPassCut2
<< std::endl;
If you also use implicit multithreading, Count automatically runs in parallel over different entries, but the final result is a single number that behaves just like any other C++ value after dereferencing.
Count always returns the number of entries after all previous Filter calls. If you want to know the number of events before a particular cut, create the Count action on the dataframe before that filter, not after.
Mean
To compute the mean of a numerical column in RDataFrame, use the Mean action. You pass the column name as a string, and RDataFrame does the rest:
ROOT::RDataFrame df("Events", "data.root");
// Mean transverse momentum for all events
auto meanPt = df.Mean("pt");
// Trigger computation
std::cout << "Mean pt = " << *meanPt << std::endl;
Mean requires that the column type is arithmetic, such as int, float, or double. It works both on original branches and on new columns created with Define. It also respects filters, so if you apply cuts before calling Mean, the average is taken only over the surviving entries:
auto df_good = df.Filter("qualityFlag == 1");
auto meanEnergyGood = df_good.Mean("energy");
std::cout << "Mean energy (good events) = " << *meanEnergyGood << std::endl;
Sometimes, a column is a collection like std::vector<float>. In that case, Mean sees each entry as a single object, not as many separate numbers. For element wise statistics you first define a scalar column using a helper function in Define, for example the average of elements in the vector, and then use Mean on that new column.
You can also request the mean through a templated version if needed:
auto meanMass = df.Mean<double>("mass");This is useful if you want to control the precision explicitly.
Mean("column") averages over events that pass all previous filters. It does not average over individual elements inside vector columns. To average elements of a vector, first create a scalar column with Define, then apply Mean to that new column.
Min
Min finds the smallest value of a given numerical column among all entries that reach this action. The usage is similar to Mean:
ROOT::RDataFrame df("Events", "data.root");
// Global minimum energy
auto minEnergy = df.Min("energy");
std::cout << "Minimum energy = " << *minEnergy << std::endl;
The returned object is, again, a lazy action result that you can dereference or query with .GetValue() once the event loop has run. If you apply filters before Min, the minimum is computed only among the filtered events:
auto df_signal = df.Filter("isSignal == 1");
auto minSignalEnergy = df_signal.Min("energy");
std::cout << "Minimum signal energy = " << *minSignalEnergy << std::endl;
Like Mean, Min expects an arithmetic column type. For collections, it treats the collection as a single element, so if you want the minimum over all items in a vector branch, define a helper column that computes that per event minimum and then call Min on it.
In rare cases, a filtered dataframe might end up with zero entries. In that situation the Min action has no data to work with and the result is not defined. You should ensure that the selection leaves at least one event or handle the empty case separately, for example by checking counts first.
You can specify the output type explicitly with the templated form:
auto minTime = df.Min<double>("time");
Min("column") returns the smallest value of that column over all events that pass the filters. If your filter removes all events, the result is undefined. Always make sure that at least one entry satisfies your selection before using Min.
Max
Max is the counterpart of Min. It returns the largest value of a numerical column for the entries considered by the current RDataFrame.
A typical use looks like this:
ROOT::RDataFrame df("Events", "data.root");
// Maximum momentum for all events
auto maxP = df.Max("p");
std::cout << "Maximum momentum = " << *maxP << std::endl;
As with all RDataFrame statistics actions, Max is lazy. The actual maximum is only computed when you request the value, and if there are several actions, RDataFrame runs a single event loop to fill them all efficiently.
Filters restrict the set of entries seen by Max:
auto df_central = df.Filter("abs(eta) < 1.0");
auto maxPtCentral = df_central.Max("pt");
std::cout << "Maximum pt in central region = " << *maxPtCentral << std::endl;You can use the templated form to control the precision of the result if needed:
auto maxEnergy = df.Max<double>("energy");
As with Min, Max expects an arithmetic column. If the dataframe has no entries after filters, the maximum is not defined. It is good practice to check with Count or design your cuts so that you know at least one event remains.
In a complete analysis you often use these simple operations together. For example, you might call Count, Mean, Min, and Max on the same filtered dataframe to obtain a quick statistical summary of a variable, all in one event loop.
Max("column") gives the largest value of that column among the events that survive your filters. If the selection yields zero events, the result is undefined. Combine Max with a Count action when you are not sure that your filtered dataset is nonempty.
Views: 9
KAHIBARO