Expanding or filtering array in TTree

Dear experts,

for my analysis I need to apply a decay tree fit that constrains a set of starting points. Since for each event there could be more than one starting point, the fit has to run more than once. The result is stored as a list in my TTree, e.g.:

root [3] t->Show(0)
...
Lb_TAU              = 0.000282896
Lb_dtflambda_PV_key = 0, 1, 2
...
Lb_dtflambda_Lambda0_pplus_PE = 17280.6, 17242.5, 25549.6

i.e. some entries are single values (such as Lb_TAU) and others are arrays (Lb_dtflambda_PV_key and Lb_dtflambda_Lambda0_pplus_PE) obeying the mapping

PV_key 0 <-> Lambda0_pplus_PE 17280.6
PV_key 1 <-> Lambda0_pplus_PE 17242.5
PV_key 2 <-> Lambda0_pplus_PE 25549.6

In fact I am only interested in values with Lb_dtflambda_PV_key == 0 but I don’t know how to filter for that without naming all >100 variables in this tree manually. To be clear: I know how to copy specific values of a TTree via TTree::SetBranchAddress but–as far as I know–it is necessary that I do this explicitly for each value of my TTree. In my case there are >100 of these variables. Is there some kind of wildcard available?

Another way could be to somehow expand and flattern the TTree, thus copying all events for each entry of Lb_dtflambda_PV_key i.e.

Lb_TAU              = 0.000282896
Lb_dtflambda_PV_key = 0
Lb_dtflambda_Lambda0_pplus_PE = 17280.6
...
Lb_TAU              = 0.000282896
Lb_dtflambda_PV_key = 1
Lb_dtflambda_Lambda0_pplus_PE = 17242.5
...
Lb_TAU              = 0.000282896
Lb_dtflambda_PV_key = 2
Lb_dtflambda_Lambda0_pplus_PE = 25549.6
...

I already tried to copy the tree with a TCut, but somehow this cut was ignored:

void test() {
    TTree *t = (TTree*) _file0->Get("sig");
    TFile *f = TFile::Open("filtered.root", "RECREATE");
    TCut cut = "Lb_dtf_PV_key == 0";
    TTree *t2 = t->CopyTree(cut);
    t2->Write();
    f->Close();
}

which I don’t understand since TTree::Draw("…", cut) works with this cut.

Can someone help me either expanding my TTree or making TTree::CopyTree(TCut) work?

Thank you for your help.

Hi, I am not a TTree::Draw expert, but if you have access to ROOT v6.10 there is an easy way to do what you want using TDataFrame. Something like this should do the trick (user and reference guide for TDataFrame are here):

#include "ROOT/TDataFrame.hxx"
int main () {
  ROOT::Experimental::TDataFrame d("sig", "inputfile.root");
  d.Filter("Lb_dtf_PV_key == 0").Snapshot("filteredSig", "filtered.root");
  return 0;
}

The event loop is executed in parallel if you add ROOT::EnableImplicitMT(); at the beginning of main.
You can also specify the filter as a c++ function, functor class or c++11 lambda but since it’s so simple in this case a string will do the job :slight_smile:

Thank you for your help. Unfortunately I wasn’t able to successfully run your snippet. What I tried with ROOT v6.11/01:

// filter.C
void filter() {
    ROOT::Experimental::TDataFrame d("dttSel_kmpip/DecayTree", "/data/stripping2dtt_MC_2012a_DD.root");
    d.Filter("Lb_dtf_PV_key == 0").Snapshot("kmpip", "stripping2dtt_MC_2012a_DD_filtered.root");
}
root [0] .L filter.C
root [1] filter()

This took ~10 min and then stops after throwing a segmentation violation.

The following commands work and result in some reasonable output:

root [0] 
Attaching file /data/stripping2dtt_MC_2012a_DD.root as _file0...
(TFile *) 0x1e16460
root [1] TTree *t = (TTree*) _file0->Get("dttSel_kmpip/DecayTree")
(TTree *) 0x1e96710
root [2] t->GetEntries()
(long long) 7530
t->Draw("Lb_dtf_PV_key", "Lb_dtf_PV_key == 0")
Info in <TCanvas::MakeDefCanvas>:  created default TCanvas with name c1
(long long) 7530

what is it, that I am doing wrong?

Hi,

could we have the input file so to try and debug your issue? The crash you are reporting is not expected.

Cheers,
D

Do you have access to AFS? I copied this file to:

/afs/cern.ch/work/n/nmeinert/public/stripping2dtt_MC_2012a_DD.root

Thanks for your help.

Hi,

looking at your code and my proposal, I realised we are doing two very different things.
If you want to make an histogram of a quantity after a cut, this is the code to be executed:

void a() {
   auto filename = "/afs/cern.ch/work/n/nmeinert/public/stripping2dtt_MC_2012a_DD.root";
   ROOT::Experimental::TDataFrame d("dttSel_kmpip/DecayTree", filename);
   auto df = d.Filter("Lb_dtf_PV_key == 0");
   auto h = df.Histo1D("Lb_dtf_PV_key");
   auto c = df.Count();
   h->Draw();
   cout << "Entries passing the cut " << *c << endl;
}

I took the freedom of explicitly counting the events passing the cuts with the Count action.

Cheers,
D

Hi,

in addition, the branch you are looking at is an array of floating point numbers

rootls -t /afs/cern.ch/work/n/nmeinert/public/stripping2dtt_MC_2012a_DD.root:dttSel_kmpip/DecayTree |grep Lb_dtf_PV_key
  Lb_dtf_PV_key                              "Lb_dtf_PV_key[Lb_dtf_nPV]/F"                              99890

So somehow one would need to pick an element of that. You can with the tdf, and this is the syntax, e.g. for element 0:

d.Filter([](std::array_view<float> v){return v[0] == 0 ;},{"Lb_dtf_PV_key"}).[... here new transformations/actions]

Cheers,
D

Thank you for your help. I don’t want to fill a histogram. I just tried this in order to convince me, that I am accessing a valid TTree and that the cut works as expected (when using TTree::Draw); sorry for this confusion.

My question was: Why does TTree::Draw works with the TCut “Lb_dtf_PV_key == 0”, whereas TTree::CopyTree does not?

I know about the array structure within the TTree, but unfortunately it is not that easy to extract the first element since the ordering within Lb_dtflambda_PV_key is not strict (I forget to mention this). Since TTree::Draw can do this trick but I don’t want to fill a histogram but filter a TTree, the question is: How do I filter the TTree, thus yielding a new TTree w.r.t. the TCut “Lb_dtf_PV_key == 0”?

Hi,

I think the elements have been all discussed above.
I think the key question is: what do you exactly mean by the condition “Lb_dtf_PV_key == 0” given that Lb_dtf_PV_key is an array?

Cheers,
D

Try:
rooteventselector --help

Refering to the example I gave in my initial post: I expect “Lb_dtf_PV_key == 0” to filter

Lb_TAU              = 0.000282896
Lb_dtflambda_PV_key = 0
Lb_dtflambda_Lambda0_pplus_PE = 17280.6

and to reject

Lb_TAU              = 0.000282896
Lb_dtflambda_PV_key = 1
Lb_dtflambda_Lambda0_pplus_PE = 17242.5

as well as

Lb_TAU              = 0.000282896
Lb_dtflambda_PV_key = 2
Lb_dtflambda_Lambda0_pplus_PE = 25549.6

or more abstract: Let us assume that the entries are:

v1 = a, b, c
v2 = x, y, z

then I expect “v1 == b” to yield:

v1 = b
v2 = y

and “v1 == c” to yield:

v1 = c
v2 = z

I know that this is not trivial since I compare an array “Lb_dtf_PV_key” with an actual value of this array (“0” in this case), but it works for TTree::Draw. The problem is, that I cannot use TTree::Draw to ‘draw’ to a new TTree.

Thank you for this tip. Unfortunately, it seems to be the same issue as with TTree::CopyTree. The cut is ignored, thus rooteventselector copies the tree. Maybe rooteventselector uses CopyTree internally?

Hi,

so do you want to be the entries of the array to be all equal to 0?
The condition

Lb_dtflambda_PV_key = 2

and alike do not mean anything since the Lb_dtflambda_PV_key is an array and not a single value

Cheers,
D

No. Please see the example in my initial post. If the array Lb_dtflambda_PV_key is {2, 0, 1} I want to extract the second element (in case of filtering Lb_dtf_PV_key == 0) of all following arrays which are guaranty to have 3 elements as well.

In case you have access to my root file, you can try this command:

root [0] 
Attaching file /data/stripping2dtt_MC_2012a_DD.root as _file0...
(TFile *) 0x2a80a00
root [1] TTree *t = (TTree*) _file0->Get("dttSel_kmpip/DecayTree")
(TTree *) 0x1f46e10
root [2] t->Draw("Lb_dtf_Lambda0_pplus_PE", "Lb_dtf_PV_key == 1")
Info in <TCanvas::MakeDefCanvas>:  created default TCanvas with name c1
(long long) 5581

This shows, that TTree::Draw is capable of doing this magic array expanding.

Yes.
I still do not understand.
You have an array, Lb_dtf_PV_key. I think we agree that this “Lb_dtf_PV_key == 0” does not have any sense, unless it means “Lb_dtf_PV_key == {0,0,0}”.

Now, you want to get, entry by entry, the index of the element of Lb_dtf_PV_key which is equal to 0: is this accurate?
If yes, you can easily do this like this:

   auto filename = "/afs/cern.ch/work/n/nmeinert/public/stripping2dtt_MC_2012a_DD.root";
   ROOT::Experimental::TDataFrame d("dttSel_kmpip/DecayTree", filename);
   auto df = d.Define("Lb_dtf_PV_key_zero_index", [](std::array_view<float> v) { int i=-1;for (el:v) {if (el==0) return i; ++i;}}  , {"Lb_dtf_PV_key"});

At this point, you will have a column in the dataframe called Lb_dtf_PV_key_zero_index. You can build at this point other quantities with this index or, alternatively filter with it:

auto df_2 = df.Filter("Lb_dtf_PV_key_zero_index == 0");
// something with df_2.

D

The misunderstanding is that “CopyTree” (and related) copies entire / complete tree entries only. So, in your case, if (in a particular tree entry) any element of the “Lb_dtf_PV_key[…]” table is “0”, this tree entry will be accepted (and its whole “Lb_dtf_PV_key[…]” table will be copied).
This is different for “Draw”. In this case, the additional “loop” which which checks “Lb_dtf_PV_key[…]” will only use that “part” of the tree entry which matches “0”.

No, “Lb_dtf_PV_key == 0” is meant to compare the values (as it does in TTree::Draw). This is not the same as comparing “Lb_dtf_PV_key == {0,0,0}” (this is never the case, thus the histogram obtained from TTree::Draw should be empty). I think TTree::Draw did something like expanding all arrays at once, maybe similar to the example I have given in my first post:

// entry #1
Lb_TAU              = 0.000282896
Lb_dtflambda_PV_key = 0
Lb_dtflambda_Lambda0_pplus_PE = 17280.6
...
// entry #2
Lb_TAU              = 0.000282896
Lb_dtflambda_PV_key = 1
Lb_dtflambda_Lambda0_pplus_PE = 17242.5
...
// entry #3
Lb_TAU              = 0.000282896
Lb_dtflambda_PV_key = 2
Lb_dtflambda_Lambda0_pplus_PE = 25549.6

Now Lb_dtflambda_PV_key is no longer an array, but a single value. I don’t know what actually is happening in TTree::Draw, but it works as expected. My question is, how do I reproduce this behavior for cloning TTrees?

Hi,

if I do not understand what you are trying to achieve, I cannot help.

Sorry.

See “Drawing expressions using arrays and array elements” in the TTree::Draw method description.