Use rdataframe Fill() with a custom object

ROOT Version: 6.24/06

I’m trying to use a custom class with a Fill method with an rdataframe but I can’t see to manage to make it work.

As a test case I adapted the code from example df001_introduction.C
I added a new class, called notTH1, for which I’m trying to use its fill method.

// ## Preparation
 
// A simple helper function to fill a test tree: this makes the example
// stand-alone.
void fill_tree(const char *treeName, const char *fileName)
{
   ROOT::RDataFrame d(10);
   int i(0);
   d.Define("b1", [&i]() { return (double)i; })
      .Define("b2",
              [&i]() {
                 auto j = i * i;
                 ++i;
                 return j;
              })
      .Snapshot(treeName, fileName);
}


class notTH1 : public TObject
{
   public:
   TH1D hist;

   notTH1()
   {
       this->hist = TH1D("h", "h", 12, -1, 11);
   }

   notTH1(const notTH1 & obj)
   {
      this->hist = TH1D(obj.hist);
   }

   Long64_t Merge(TCollection * list)
   {
      TList hist_list;
      for (const auto&&  item : *list)
      {
         notTH1* temp = (notTH1*) item;
         hist_list.Add( &(temp->hist) );
      }
      return this->hist.Merge(&hist_list);
   }

   void Fill(const double & Value)
   {
      this->hist.Fill(Value);
   }
};
 
int dataframe_fill()
{
 
   // We prepare an input tree to run on
   auto fileName = "test_myfill.root";
   auto treeName = "myTree";
   fill_tree(treeName, fileName);
 
   // We read the tree from the file and create a RDataFrame, a class that
   // allows us to interact with the data contained in the tree.
   // We select a default column, a *branch* to adopt ROOT jargon, which will
   // be looked at if none is specified by the user when dealing with filters
   // and actions.
   ROOT::RDataFrame d(treeName, fileName, {"b1"});
 
   // ## Operations on the dataframe
    
   // ### `Foreach` action
   // The most generic action of all: an operation is applied to all entries.
   // In this case we fill a histogram. In some sense this is a violation of a
   // purely functional paradigm - C++ allows to do that.
   notTH1 h1;
   d.Filter([](int b2) { return b2 % 2 == 0; }, {"b2"}).Foreach([&h1](double b1) { h1.Fill(b1); });
 
   std::cout << "Filled h1 with " << h1.hist.GetEntries() << " entries" << std::endl;
 
   // ### `Fill` action
   notTH1 h2;
   d.Filter([](int b2) { return b2 % 2 == 0; }, {"b2"}).Fill<double>(h2, {"b2"});

   std::cout << "Filled h2 with " << h2.hist.GetEntries() << " entries" << std::endl;


   TCanvas* c1 = new TCanvas();
   h1.hist.DrawClone();

   TCanvas* c2 = new TCanvas();
   h2.hist.DrawClone();


   return 0;
}

The process works well using the Foreach method but fails with Fill.
I get many errors but here the ones where I believe the problem is:

In module 'std' imported from input_line_1:1:
/usr/include/c++/9/bits/allocator.h:118:24: error: 'const_pointer' declared as a pointer to a reference of type 'notTH1 &'
      typedef const _Tp* const_pointer;
                       ^
In module 'std' imported from input_line_1:1:
/usr/include/c++/9/bits/shared_ptr.h:255:9: error: type '__shared_ptr<notTH1 &>' is not a direct or virtual base of 'std::shared_ptr<notTH1 &>'
      : __shared_ptr<_Tp>(std::move(__r)) { }
        ^~~~~~~~~~~~~~~~~
/home/home/opt/root/include/ROOT/RDF/RInterface.hxx:1504:16: note: in instantiation of member function 'std::shared_ptr<notTH1 &>::shared_ptr' requested here
      auto h = std::make_shared<T>(std::forward<T>(model));
               ^
/home/home/data/dataframe_fill.cpp:88:57: note: in instantiation of function template specialization 'ROOT::RDF::RInterface<ROOT::Detail::RDF::RFilter<(lambda at /home/home/data/dataframe_fill.cpp:88:13), ROOT::Detail::RDF::RLoopManager>, void>::Fill<double, notTH1 &>' requested here
   d.Filter([](int b2) { return b2 % 2 == 0; }, {"b2"}).Fill<double>(h2, {"b2"});
                                                        ^

Can someone help in understanding what I’m doing wrong?

Hi @presbitero ,
sorry for the high latency, I was off last week.

I cannot reproduce the error you shared with the latest ROOT. Can you try your code with a nightly build (available at Nightlies - ROOT )?

Running with ROOT master I see two problems with the program logic:

   notTH1 h2;
   d.Filter([](int b2) { return b2 % 2 == 0; }, {"b2"}).Fill<double>(h2, {"b2"});

should be

   notTH1 h2;
   auto res = d.Filter([](int b2) { return b2 % 2 == 0; }, {"b2"}).Fill<int>(h2, {"b2"});

and you should then use res, not h2: res is a pointer to the filled object, which is a clone of what you pass to Fill. Maybe we should make this more clear at ROOT: ROOT::RDF::RInterface< Proxied, DataSource > Class Template Reference .

  1. Fill<double> should be Fill<int> because "b2" is of type int, but RDF will give a readable error message in this case.

I hope this helps!
Cheers,
Enrico

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.