KAHIBARO
Discord Login Register

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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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

Comments

Please login to add a comment.

Don't have an account? Register now!