|
|
|||
File indexing completed on 2026-09-25 09:28:14
0001 // Author: Enrico Guiraud, Danilo Piparo CERN 03/2017 0002 0003 /************************************************************************* 0004 * Copyright (C) 1995-2021, Rene Brun and Fons Rademakers. * 0005 * All rights reserved. * 0006 * * 0007 * For the licensing terms see $ROOTSYS/LICENSE. * 0008 * For the list of contributors see $ROOTSYS/README/CREDITS. * 0009 *************************************************************************/ 0010 0011 #ifndef ROOT_RDF_TINTERFACE 0012 #define ROOT_RDF_TINTERFACE 0013 0014 #include "ROOT/RDataSource.hxx" 0015 #include "ROOT/RDF/ActionHelpers.hxx" 0016 #include "ROOT/RDF/HistoModels.hxx" 0017 #include "ROOT/RDF/InterfaceUtils.hxx" 0018 #include "ROOT/RDF/RColumnRegister.hxx" 0019 #include "ROOT/RDF/RDefaultValueFor.hxx" 0020 #include "ROOT/RDF/RDefine.hxx" 0021 #include "ROOT/RDF/RDefinePerSample.hxx" 0022 #include "ROOT/RDF/RFilter.hxx" 0023 #include "ROOT/RDF/RInterfaceBase.hxx" 0024 #include "ROOT/RDF/RVariation.hxx" 0025 #include "ROOT/RDF/RLazyDSImpl.hxx" 0026 #include "ROOT/RDF/RLoopManager.hxx" 0027 #include "ROOT/RDF/RRange.hxx" 0028 #include "ROOT/RDF/RFilterWithMissingValues.hxx" 0029 #include "ROOT/RDF/Utils.hxx" 0030 #include "ROOT/RDF/RDFDescription.hxx" 0031 #include "ROOT/RDF/RVariationsDescription.hxx" 0032 #include "ROOT/RResultPtr.hxx" 0033 #include "ROOT/RSnapshotOptions.hxx" 0034 #include <string_view> 0035 #include "ROOT/RVec.hxx" 0036 #include "ROOT/TypeTraits.hxx" 0037 #include "RtypesCore.h" // for ULong64_t 0038 #include "TDirectory.h" 0039 #include "TH1.h" // For Histo actions 0040 #include "TH2.h" // For Histo actions 0041 #include "TH3.h" // For Histo actions 0042 #include "THn.h" 0043 #include "THnSparse.h" 0044 #include "TProfile.h" 0045 #include "TProfile2D.h" 0046 #include "TStatistic.h" 0047 0048 #include "RConfigure.h" // for R__HAS_ROOT7 0049 #ifdef R__HAS_ROOT7 0050 #include <ROOT/RBinWithError.hxx> 0051 #include <ROOT/RHist.hxx> 0052 #include <ROOT/RHistEngine.hxx> 0053 #endif 0054 0055 #include <algorithm> 0056 #include <cstddef> 0057 #include <initializer_list> 0058 #include <iterator> // std::back_insterter 0059 #include <limits> 0060 #include <memory> 0061 #include <set> 0062 #include <sstream> 0063 #include <stdexcept> 0064 #include <string> 0065 #include <type_traits> // is_same, enable_if 0066 #include <typeinfo> 0067 #include <unordered_set> 0068 #include <utility> // std::index_sequence 0069 #include <vector> 0070 #include <any> 0071 0072 class TGraph; 0073 0074 // Windows requires a forward decl of printValue to accept it as a valid friend function in RInterface 0075 namespace ROOT { 0076 void DisableImplicitMT(); 0077 bool IsImplicitMTEnabled(); 0078 void EnableImplicitMT(UInt_t numthreads); 0079 class RDataFrame; 0080 } // namespace ROOT 0081 namespace cling { 0082 std::string printValue(ROOT::RDataFrame *tdf); 0083 } 0084 0085 namespace ROOT { 0086 namespace RDF { 0087 namespace RDFDetail = ROOT::Detail::RDF; 0088 namespace RDFInternal = ROOT::Internal::RDF; 0089 namespace TTraits = ROOT::TypeTraits; 0090 0091 template <typename Proxied> 0092 class RInterface; 0093 0094 using RNode = RInterface<::ROOT::Detail::RDF::RNodeBase>; 0095 } // namespace RDF 0096 0097 namespace Internal { 0098 namespace RDF { 0099 class GraphCreatorHelper; 0100 void ChangeEmptyEntryRange(const ROOT::RDF::RNode &node, std::pair<ULong64_t, ULong64_t> &&newRange); 0101 void ChangeBeginAndEndEntries(const RNode &node, Long64_t begin, Long64_t end); 0102 void ChangeSpec(const ROOT::RDF::RNode &node, ROOT::RDF::Experimental::RDatasetSpec &&spec); 0103 std::vector<std::pair<std::uint64_t, std::uint64_t>> GetDatasetGlobalClusterBoundaries(const RNode &node); 0104 void TriggerRun(ROOT::RDF::RNode node); 0105 std::string GetDataSourceLabel(const ROOT::RDF::RNode &node); 0106 void SetTTreeLifeline(ROOT::RDF::RNode &node, std::any lifeline); 0107 } // namespace RDF 0108 } // namespace Internal 0109 0110 namespace RDF { 0111 0112 // clang-format off 0113 /** 0114 * \class ROOT::RDF::RInterface 0115 * \ingroup dataframe 0116 * \brief The public interface to the RDataFrame federation of classes. 0117 * \tparam Proxied One of the "node" base types (e.g. RLoopManager, RFilterBase). The user never specifies this type manually. 0118 * 0119 * The documentation of each method features a one liner illustrating how to use the method, for example showing how 0120 * the majority of the template parameters are automatically deduced requiring no or very little effort by the user. 0121 */ 0122 // clang-format on 0123 template <typename Proxied> 0124 class RInterface : public RInterfaceBase { 0125 using RFilterBase = RDFDetail::RFilterBase; 0126 using RRangeBase = RDFDetail::RRangeBase; 0127 using RLoopManager = RDFDetail::RLoopManager; 0128 friend std::string cling::printValue(::ROOT::RDataFrame *tdf); // For a nice printing at the prompt 0129 friend class RDFInternal::GraphDrawing::GraphCreatorHelper; 0130 0131 template <typename T> 0132 friend class RInterface; 0133 0134 friend void RDFInternal::TriggerRun(RNode node); 0135 friend void RDFInternal::ChangeEmptyEntryRange(const RNode &node, std::pair<ULong64_t, ULong64_t> &&newRange); 0136 friend void RDFInternal::ChangeBeginAndEndEntries(const RNode &node, Long64_t start, Long64_t end); 0137 friend void RDFInternal::ChangeSpec(const RNode &node, ROOT::RDF::Experimental::RDatasetSpec &&spec); 0138 friend std::vector<std::pair<std::uint64_t, std::uint64_t>> 0139 RDFInternal::GetDatasetGlobalClusterBoundaries(const RNode &node); 0140 friend std::string ROOT::Internal::RDF::GetDataSourceLabel(const RNode &node); 0141 friend void ROOT::Internal::RDF::SetTTreeLifeline(ROOT::RDF::RNode &node, std::any lifeline); 0142 std::shared_ptr<Proxied> fProxiedPtr; ///< Smart pointer to the graph node encapsulated by this RInterface. 0143 0144 public: 0145 //////////////////////////////////////////////////////////////////////////// 0146 /// \brief Copy-assignment operator for RInterface. 0147 RInterface &operator=(const RInterface &) = default; 0148 0149 //////////////////////////////////////////////////////////////////////////// 0150 /// \brief Copy-ctor for RInterface. 0151 RInterface(const RInterface &) = default; 0152 0153 //////////////////////////////////////////////////////////////////////////// 0154 /// \brief Move-ctor for RInterface. 0155 RInterface(RInterface &&) = default; 0156 0157 //////////////////////////////////////////////////////////////////////////// 0158 /// \brief Move-assignment operator for RInterface. 0159 RInterface &operator=(RInterface &&) = default; 0160 0161 //////////////////////////////////////////////////////////////////////////// 0162 /// \brief Build a RInterface from a RLoopManager. 0163 /// This constructor is only available for RInterface<RLoopManager>. 0164 template <typename T = Proxied, typename = std::enable_if_t<std::is_same<T, RLoopManager>::value, int>> 0165 RInterface(const std::shared_ptr<RLoopManager> &proxied) : RInterfaceBase(proxied), fProxiedPtr(proxied) 0166 { 0167 } 0168 0169 //////////////////////////////////////////////////////////////////////////// 0170 /// \brief Cast any RDataFrame node to a common type ROOT::RDF::RNode. 0171 /// Different RDataFrame methods return different C++ types. All nodes, however, 0172 /// can be cast to this common type at the cost of a small performance penalty. 0173 /// This allows, for example, storing RDataFrame nodes in a vector, or passing them 0174 /// around via (non-template, C++11) helper functions. 0175 /// Example usage: 0176 /// ~~~{.cpp} 0177 /// // a function that conditionally adds a Range to a RDataFrame node. 0178 /// RNode MaybeAddRange(RNode df, bool mustAddRange) 0179 /// { 0180 /// return mustAddRange ? df.Range(1) : df; 0181 /// } 0182 /// // use as : 0183 /// ROOT::RDataFrame df(10); 0184 /// auto maybeRanged = MaybeAddRange(df, true); 0185 /// ~~~ 0186 /// Note that it is not a problem to pass RNode's by value. 0187 operator RNode() const 0188 { 0189 return RNode(std::static_pointer_cast<::ROOT::Detail::RDF::RNodeBase>(fProxiedPtr), *fLoopManager, fColRegister); 0190 } 0191 0192 //////////////////////////////////////////////////////////////////////////// 0193 /// \brief Append a filter to the call graph. 0194 /// \param[in] f Function, lambda expression, functor class or any other callable object. It must return a `bool` 0195 /// signalling whether the event has passed the selection (true) or not (false). 0196 /// \param[in] columns Names of the columns/branches in input to the filter function. 0197 /// \param[in] name Optional name of this filter. See `Report`. 0198 /// \return the filter node of the computation graph. 0199 /// 0200 /// Append a filter node at the point of the call graph corresponding to the 0201 /// object this method is called on. 0202 /// The callable `f` should not have side-effects (e.g. modification of an 0203 /// external or static variable) to ensure correct results when implicit 0204 /// multi-threading is active. 0205 /// 0206 /// RDataFrame only evaluates filters when necessary: if multiple filters 0207 /// are chained one after another, they are executed in order and the first 0208 /// one returning false causes the event to be discarded. 0209 /// Even if multiple actions or transformations depend on the same filter, 0210 /// it is executed once per entry. If its result is requested more than 0211 /// once, the cached result is served. 0212 /// 0213 /// ### Example usage: 0214 /// ~~~{.cpp} 0215 /// // C++ callable (function, functor class, lambda...) that takes two parameters of the types of "x" and "y" 0216 /// auto filtered = df.Filter(myCut, {"x", "y"}); 0217 /// 0218 /// // String: it must contain valid C++ except that column names can be used instead of variable names 0219 /// auto filtered = df.Filter("x*y > 0"); 0220 /// ~~~ 0221 /// 0222 /// \note If the body of the string expression contains an explicit `return` statement (even if it is in a nested 0223 /// scope), RDataFrame _will not_ add another one in front of the expression. So this will not work: 0224 /// ~~~{.cpp} 0225 /// df.Filter("Sum(Map(vec, [](float e) { return e*e > 0.5; }))") 0226 /// ~~~ 0227 /// but instead this will: 0228 /// ~~~{.cpp} 0229 /// df.Filter("return Sum(Map(vec, [](float e) { return e*e > 0.5; }))") 0230 /// ~~~ 0231 template <typename F, std::enable_if_t<!std::is_convertible<F, std::string>::value, int> = 0> 0232 RInterface<RDFDetail::RFilter<F, Proxied>> Filter(F f, const ColumnNames_t &columns = {}, std::string_view name = "") 0233 { 0234 RDFInternal::CheckFilter(f); 0235 using ColTypes_t = typename TTraits::CallableTraits<F>::arg_types; 0236 constexpr auto nColumns = ColTypes_t::list_size; 0237 const auto validColumnNames = GetValidatedColumnNames(nColumns, columns); 0238 CheckAndFillDSColumns(validColumnNames, ColTypes_t()); 0239 0240 using F_t = RDFDetail::RFilter<F, Proxied>; 0241 0242 auto filterPtr = std::make_shared<F_t>(std::move(f), validColumnNames, fProxiedPtr, fColRegister, name); 0243 return RInterface<F_t>(std::move(filterPtr), *fLoopManager, fColRegister); 0244 } 0245 0246 //////////////////////////////////////////////////////////////////////////// 0247 /// \brief Append a filter to the call graph. 0248 /// \param[in] f Function, lambda expression, functor class or any other callable object. It must return a `bool` 0249 /// signalling whether the event has passed the selection (true) or not (false). 0250 /// \param[in] name Optional name of this filter. See `Report`. 0251 /// \return the filter node of the computation graph. 0252 /// 0253 /// Refer to the first overload of this method for the full documentation. 0254 template <typename F, std::enable_if_t<!std::is_convertible<F, std::string>::value, int> = 0> 0255 RInterface<RDFDetail::RFilter<F, Proxied>> Filter(F f, std::string_view name) 0256 { 0257 // The sfinae is there in order to pick up the overloaded method which accepts two strings 0258 // rather than this template method. 0259 return Filter(f, {}, name); 0260 } 0261 0262 //////////////////////////////////////////////////////////////////////////// 0263 /// \brief Append a filter to the call graph. 0264 /// \param[in] f Function, lambda expression, functor class or any other callable object. It must return a `bool` 0265 /// signalling whether the event has passed the selection (true) or not (false). 0266 /// \param[in] columns Names of the columns/branches in input to the filter function. 0267 /// \return the filter node of the computation graph. 0268 /// 0269 /// Refer to the first overload of this method for the full documentation. 0270 template <typename F> 0271 RInterface<RDFDetail::RFilter<F, Proxied>> Filter(F f, const std::initializer_list<std::string> &columns) 0272 { 0273 return Filter(f, ColumnNames_t{columns}); 0274 } 0275 0276 //////////////////////////////////////////////////////////////////////////// 0277 /// \brief Append a filter to the call graph. 0278 /// \param[in] expression The filter expression in C++ 0279 /// \param[in] name Optional name of this filter. See `Report`. 0280 /// \return the filter node of the computation graph. 0281 /// 0282 /// The expression is just-in-time compiled and used to filter entries. It must 0283 /// be valid C++ syntax in which variable names are substituted with the names 0284 /// of branches/columns. 0285 /// 0286 /// ### Example usage: 0287 /// ~~~{.cpp} 0288 /// auto filtered_df = df.Filter("myCollection.size() > 3"); 0289 /// auto filtered_name_df = df.Filter("myCollection.size() > 3", "Minumum collection size"); 0290 /// ~~~ 0291 /// 0292 /// \note If the body of the string expression contains an explicit `return` statement (even if it is in a nested 0293 /// scope), RDataFrame _will not_ add another one in front of the expression. So this will not work: 0294 /// ~~~{.cpp} 0295 /// df.Filter("Sum(Map(vec, [](float e) { return e*e > 0.5; }))") 0296 /// ~~~ 0297 /// but instead this will: 0298 /// ~~~{.cpp} 0299 /// df.Filter("return Sum(Map(vec, [](float e) { return e*e > 0.5; }))") 0300 /// ~~~ 0301 RInterface<RDFDetail::RJittedFilter> Filter(std::string_view expression, std::string_view name = "") 0302 { 0303 const auto jittedFilter = RDFInternal::BookFilterJit(RDFInternal::UpcastNode(fProxiedPtr), name, expression, 0304 fColRegister, nullptr, GetDataSource()); 0305 0306 return RInterface<RDFDetail::RJittedFilter>(std::move(jittedFilter), *fLoopManager, fColRegister); 0307 } 0308 0309 //////////////////////////////////////////////////////////////////////////// 0310 /// \brief Discard entries with missing values 0311 /// \param[in] column Column name whose entries with missing values should be discarded 0312 /// \return The filter node of the computation graph 0313 /// 0314 /// This operation is useful in case an entry of the dataset is incomplete, 0315 /// i.e. if one or more of the columns do not have valid values. If the value 0316 /// of the input column is missing for an entry, the entire entry will be 0317 /// discarded from the rest of this branch of the computation graph. 0318 /// 0319 /// Use cases include: 0320 /// * When processing multiple files, one or more of them is missing a column 0321 /// * In horizontal joining with entry matching, a certain dataset has no 0322 /// match for the current entry. 0323 /// 0324 /// ### Example usage: 0325 /// 0326 /// \code{.py} 0327 /// # Assume a dataset with columns [idx, x] matching another dataset with 0328 /// # columns [idx, y]. For idx == 42, the right-hand dataset has no match 0329 /// df = ROOT.RDataFrame(dataset) 0330 /// df_nomissing = df.FilterAvailable("idx").Define("z", "x + y") 0331 /// colz = df_nomissing.Take[int]("z") 0332 /// \endcode 0333 /// 0334 /// \code{.cpp} 0335 /// // Assume a dataset with columns [idx, x] matching another dataset with 0336 /// // columns [idx, y]. For idx == 42, the right-hand dataset has no match 0337 /// ROOT::RDataFrame df{dataset}; 0338 /// auto df_nomissing = df.FilterAvailable("idx") 0339 /// .Define("z", [](int x, int y) { return x + y; }, {"x", "y"}); 0340 /// auto colz = df_nomissing.Take<int>("z"); 0341 /// \endcode 0342 /// 0343 /// \note See FilterMissing() if you want to keep only the entries with 0344 /// missing values instead. 0345 RInterface<RDFDetail::RFilterWithMissingValues<Proxied>> FilterAvailable(std::string_view column) 0346 { 0347 const auto columns = ColumnNames_t{column.data()}; 0348 // For now disable this functionality in case of an empty data source and 0349 // the column name was not defined previously. 0350 if (ROOT::Internal::RDF::GetDataSourceLabel(*this) == "EmptyDS") 0351 throw std::runtime_error("Unknown column: \"" + std::string(column) + "\""); 0352 using F_t = RDFDetail::RFilterWithMissingValues<Proxied>; 0353 auto filterPtr = std::make_shared<F_t>(/*discardEntry*/ true, fProxiedPtr, fColRegister, columns); 0354 CheckAndFillDSColumns(columns, TTraits::TypeList<void>{}); 0355 return RInterface<F_t>(std::move(filterPtr), *fLoopManager, fColRegister); 0356 } 0357 0358 //////////////////////////////////////////////////////////////////////////// 0359 /// \brief Keep only the entries that have missing values. 0360 /// \param[in] column Column name whose entries with missing values should be kept 0361 /// \return The filter node of the computation graph 0362 /// 0363 /// This operation is useful in case an entry of the dataset is incomplete, 0364 /// i.e. if one or more of the columns do not have valid values. It only 0365 /// keeps the entries for which the value of the input column is missing. 0366 /// 0367 /// Use cases include: 0368 /// * When processing multiple files, one or more of them is missing a column 0369 /// * In horizontal joining with entry matching, a certain dataset has no 0370 /// match for the current entry. 0371 /// 0372 /// ### Example usage: 0373 /// 0374 /// \code{.py} 0375 /// # Assume a dataset made of two files vertically chained together, one has 0376 /// # column "x" and the other has column "y" 0377 /// df = ROOT.RDataFrame(dataset) 0378 /// df_valid_col_x = df.FilterMissing("y") 0379 /// df_valid_col_y = df.FilterMissing("x") 0380 /// display_x = df_valid_col_x.Display(("x",)) 0381 /// display_y = df_valid_col_y.Display(("y",)) 0382 /// \endcode 0383 /// 0384 /// \code{.cpp} 0385 /// // Assume a dataset made of two files vertically chained together, one has 0386 /// // column "x" and the other has column "y" 0387 /// ROOT.RDataFrame df{dataset}; 0388 /// auto df_valid_col_x = df.FilterMissing("y"); 0389 /// auto df_valid_col_y = df.FilterMissing("x"); 0390 /// auto display_x = df_valid_col_x.Display<int>({"x"}); 0391 /// auto display_y = df_valid_col_y.Display<int>({"y"}); 0392 /// \endcode 0393 /// 0394 /// \note See FilterAvailable() if you want to discard the entries in case 0395 /// there is a missing value instead. 0396 RInterface<RDFDetail::RFilterWithMissingValues<Proxied>> FilterMissing(std::string_view column) 0397 { 0398 const auto columns = ColumnNames_t{column.data()}; 0399 // For now disable this functionality in case of an empty data source and 0400 // the column name was not defined previously. 0401 if (ROOT::Internal::RDF::GetDataSourceLabel(*this) == "EmptyDS") 0402 throw std::runtime_error("Unknown column: \"" + std::string(column) + "\""); 0403 using F_t = RDFDetail::RFilterWithMissingValues<Proxied>; 0404 auto filterPtr = std::make_shared<F_t>(/*discardEntry*/ false, fProxiedPtr, fColRegister, columns); 0405 CheckAndFillDSColumns(columns, TTraits::TypeList<void>{}); 0406 return RInterface<F_t>(std::move(filterPtr), *fLoopManager, fColRegister); 0407 } 0408 0409 // clang-format off 0410 //////////////////////////////////////////////////////////////////////////// 0411 /// \brief Define a new column. 0412 /// \param[in] name The name of the defined column. 0413 /// \param[in] expression Function, lambda expression, functor class or any other callable object producing the defined value. Returns the value that will be assigned to the defined column. This callable must be thread safe when used with multiple threads. 0414 /// \param[in] columns Names of the columns/branches in input to the producer function. 0415 /// \return the first node of the computation graph for which the new quantity is defined. 0416 /// 0417 /// Define a column that will be visible from all subsequent nodes 0418 /// of the functional chain. The `expression` is only evaluated for entries that pass 0419 /// all the preceding filters. 0420 /// A new variable is created called `name`, accessible as if it was contained 0421 /// in the dataset from subsequent transformations/actions. 0422 /// 0423 /// Use cases include: 0424 /// * caching the results of complex calculations for easy and efficient multiple access 0425 /// * extraction of quantities of interest from complex objects 0426 /// 0427 /// An exception is thrown if the name of the new column is already in use in this branch of the computation graph. 0428 /// Note that the callable must be thread safe when called from multiple threads. Use DefineSlot() if needed. 0429 /// 0430 /// ### Example usage: 0431 /// ~~~{.cpp} 0432 /// // assuming a function with signature: 0433 /// double myComplexCalculation(const RVec<float> &muon_pts); 0434 /// // we can pass it directly to Define 0435 /// auto df_with_define = df.Define("newColumn", myComplexCalculation, {"muon_pts"}); 0436 /// // alternatively, we can pass the body of the function as a string, as in Filter: 0437 /// auto df_with_define = df.Define("newColumn", "x*x + y*y"); 0438 /// ~~~ 0439 /// 0440 /// \note If the body of the string expression contains an explicit `return` statement (even if it is in a nested 0441 /// scope), RDataFrame _will not_ add another one in front of the expression. So this will not work: 0442 /// ~~~{.cpp} 0443 /// df.Define("x2", "Map(v, [](float e) { return e*e; })") 0444 /// ~~~ 0445 /// but instead this will: 0446 /// ~~~{.cpp} 0447 /// df.Define("x2", "return Map(v, [](float e) { return e*e; })") 0448 /// ~~~ 0449 template <typename F, typename std::enable_if_t<!std::is_convertible<F, std::string>::value, int> = 0> 0450 RInterface<Proxied> Define(std::string_view name, F expression, const ColumnNames_t &columns = {}) 0451 { 0452 return DefineImpl<F, RDFDetail::ExtraArgsForDefine::None>(name, std::move(expression), columns, "Define"); 0453 } 0454 // clang-format on 0455 0456 // clang-format off 0457 //////////////////////////////////////////////////////////////////////////// 0458 /// \brief Define a new column with a value dependent on the processing slot. 0459 /// \param[in] name The name of the defined column. 0460 /// \param[in] expression Function, lambda expression, functor class or any other callable object producing the defined value. Returns the value that will be assigned to the defined column. 0461 /// \param[in] columns Names of the columns/branches in input to the producer function (excluding the slot number). 0462 /// \return the first node of the computation graph for which the new quantity is defined. 0463 /// 0464 /// This alternative implementation of `Define` is meant as a helper to evaluate new column values in a thread-safe manner. 0465 /// The expression must be a callable of signature R(unsigned int, T1, T2, ...) where `T1, T2...` are the types 0466 /// of the columns that the expression takes as input. The first parameter is reserved for an unsigned integer 0467 /// representing a "slot number". RDataFrame guarantees that different threads will invoke the expression with 0468 /// different slot numbers - slot numbers will range from zero to ROOT::GetThreadPoolSize()-1. 0469 /// Note that there is no guarantee as to how often each slot will be reached during the event loop. 0470 /// 0471 /// The following two calls are equivalent, although `DefineSlot` is slightly more performant: 0472 /// ~~~{.cpp} 0473 /// int function(unsigned int, double, double); 0474 /// df.Define("x", function, {"rdfslot_", "column1", "column2"}) 0475 /// df.DefineSlot("x", function, {"column1", "column2"}) 0476 /// ~~~ 0477 /// 0478 /// See Define() for more information. 0479 template <typename F> 0480 RInterface<Proxied> DefineSlot(std::string_view name, F expression, const ColumnNames_t &columns = {}) 0481 { 0482 return DefineImpl<F, RDFDetail::ExtraArgsForDefine::Slot>(name, std::move(expression), columns, "DefineSlot"); 0483 } 0484 // clang-format on 0485 0486 // clang-format off 0487 //////////////////////////////////////////////////////////////////////////// 0488 /// \brief Define a new column with a value dependent on the processing slot and the current entry. 0489 /// \param[in] name The name of the defined column. 0490 /// \param[in] expression Function, lambda expression, functor class or any other callable object producing the defined value. Returns the value that will be assigned to the defined column. 0491 /// \param[in] columns Names of the columns/branches in input to the producer function (excluding slot and entry). 0492 /// \return the first node of the computation graph for which the new quantity is defined. 0493 /// 0494 /// This alternative implementation of `Define` is meant as a helper in writing entry-specific, thread-safe custom 0495 /// columns. The expression must be a callable of signature R(unsigned int, ULong64_t, T1, T2, ...) where `T1, T2...` 0496 /// are the types of the columns that the expression takes as input. The first parameter is reserved for an unsigned 0497 /// integer representing a "slot number". RDataFrame guarantees that different threads will invoke the expression with 0498 /// different slot numbers - slot numbers will range from zero to ROOT::GetThreadPoolSize()-1. 0499 /// Note that there is no guarantee as to how often each slot will be reached during the event loop. 0500 /// The second parameter is reserved for a `ULong64_t` representing the current entry being processed by the current thread. 0501 /// 0502 /// The following two `Define`s are equivalent, although `DefineSlotEntry` is slightly more performant: 0503 /// ~~~{.cpp} 0504 /// int function(unsigned int, ULong64_t, double, double); 0505 /// Define("x", function, {"rdfslot_", "rdfentry_", "column1", "column2"}) 0506 /// DefineSlotEntry("x", function, {"column1", "column2"}) 0507 /// ~~~ 0508 /// 0509 /// See Define() for more information. 0510 template <typename F> 0511 RInterface<Proxied> DefineSlotEntry(std::string_view name, F expression, const ColumnNames_t &columns = {}) 0512 { 0513 return DefineImpl<F, RDFDetail::ExtraArgsForDefine::SlotAndEntry>(name, std::move(expression), columns, 0514 "DefineSlotEntry"); 0515 } 0516 // clang-format on 0517 0518 //////////////////////////////////////////////////////////////////////////// 0519 /// \brief Define a new column. 0520 /// \param[in] name The name of the defined column. 0521 /// \param[in] expression An expression in C++ which represents the defined value 0522 /// \return the first node of the computation graph for which the new quantity is defined. 0523 /// 0524 /// The expression is just-in-time compiled and used to produce the column entries. 0525 /// It must be valid C++ syntax in which variable names are substituted with the names 0526 /// of branches/columns. 0527 /// 0528 /// \note If the body of the string expression contains an explicit `return` statement (even if it is in a nested 0529 /// scope), RDataFrame _will not_ add another one in front of the expression. So this will not work: 0530 /// ~~~{.cpp} 0531 /// df.Define("x2", "Map(v, [](float e) { return e*e; })") 0532 /// ~~~ 0533 /// but instead this will: 0534 /// ~~~{.cpp} 0535 /// df.Define("x2", "return Map(v, [](float e) { return e*e; })") 0536 /// ~~~ 0537 /// 0538 /// Refer to the first overload of this method for the full documentation. 0539 RInterface<Proxied> Define(std::string_view name, std::string_view expression) 0540 { 0541 constexpr auto where = "Define"; 0542 RDFInternal::CheckValidCppVarName(name, where); 0543 // these checks must be done before jitting lest we throw exceptions in jitted code 0544 RDFInternal::CheckForRedefinition(where, name, fColRegister, 0545 GetDataSource() ? GetDataSource()->GetColumnNames() : ColumnNames_t{}); 0546 0547 auto jittedDefine = RDFInternal::BookDefineJit(name, expression, *fLoopManager, GetDataSource(), fColRegister); 0548 0549 RDFInternal::RColumnRegister newCols(fColRegister); 0550 newCols.AddDefine(std::move(jittedDefine)); 0551 0552 RInterface<Proxied> newInterface(fProxiedPtr, *fLoopManager, std::move(newCols)); 0553 0554 return newInterface; 0555 } 0556 0557 //////////////////////////////////////////////////////////////////////////// 0558 /// \brief Overwrite the value and/or type of an existing column. 0559 /// \param[in] name The name of the column to redefine. 0560 /// \param[in] expression Function, lambda expression, functor class or any other callable object producing the defined value. Returns the value that will be assigned to the defined column. 0561 /// \param[in] columns Names of the columns/branches in input to the expression. 0562 /// \return the first node of the computation graph for which the quantity is redefined. 0563 /// 0564 /// The old value of the column can be used as an input for the expression. 0565 /// 0566 /// An exception is thrown in case the column to redefine does not already exist. 0567 /// See Define() for more information. 0568 template <typename F, std::enable_if_t<!std::is_convertible<F, std::string>::value, int> = 0> 0569 RInterface<Proxied> Redefine(std::string_view name, F expression, const ColumnNames_t &columns = {}) 0570 { 0571 return DefineImpl<F, RDFDetail::ExtraArgsForDefine::None>(name, std::move(expression), columns, "Redefine"); 0572 } 0573 0574 // clang-format off 0575 //////////////////////////////////////////////////////////////////////////// 0576 /// \brief Overwrite the value and/or type of an existing column. 0577 /// \param[in] name The name of the column to redefine. 0578 /// \param[in] expression Function, lambda expression, functor class or any other callable object producing the defined value. Returns the value that will be assigned to the defined column. 0579 /// \param[in] columns Names of the columns/branches in input to the producer function (excluding slot). 0580 /// \return the first node of the computation graph for which the new quantity is defined. 0581 /// 0582 /// The old value of the column can be used as an input for the expression. 0583 /// An exception is thrown in case the column to redefine does not already exist. 0584 /// 0585 /// See DefineSlot() for more information. 0586 // clang-format on 0587 template <typename F> 0588 RInterface<Proxied> RedefineSlot(std::string_view name, F expression, const ColumnNames_t &columns = {}) 0589 { 0590 return DefineImpl<F, RDFDetail::ExtraArgsForDefine::Slot>(name, std::move(expression), columns, "RedefineSlot"); 0591 } 0592 0593 // clang-format off 0594 //////////////////////////////////////////////////////////////////////////// 0595 /// \brief Overwrite the value and/or type of an existing column. 0596 /// \param[in] name The name of the column to redefine. 0597 /// \param[in] expression Function, lambda expression, functor class or any other callable object producing the defined value. Returns the value that will be assigned to the defined column. 0598 /// \param[in] columns Names of the columns/branches in input to the producer function (excluding slot and entry). 0599 /// \return the first node of the computation graph for which the new quantity is defined. 0600 /// 0601 /// The old value of the column can be used as an input for the expression. 0602 /// An exception is thrown in case the column to re-define does not already exist. 0603 /// 0604 /// See DefineSlotEntry() for more information. 0605 // clang-format on 0606 template <typename F> 0607 RInterface<Proxied> RedefineSlotEntry(std::string_view name, F expression, const ColumnNames_t &columns = {}) 0608 { 0609 return DefineImpl<F, RDFDetail::ExtraArgsForDefine::SlotAndEntry>(name, std::move(expression), columns, 0610 "RedefineSlotEntry"); 0611 } 0612 0613 //////////////////////////////////////////////////////////////////////////// 0614 /// \brief Overwrite the value and/or type of an existing column. 0615 /// \param[in] name The name of the column to redefine. 0616 /// \param[in] expression An expression in C++ which represents the defined value 0617 /// \return the first node of the computation graph for which the new quantity is defined. 0618 /// 0619 /// The expression is just-in-time compiled and used to produce the column entries. 0620 /// It must be valid C++ syntax in which variable names are substituted with the names 0621 /// of branches/columns. 0622 /// 0623 /// The old value of the column can be used as an input for the expression. 0624 /// An exception is thrown in case the column to re-define does not already exist. 0625 /// 0626 /// Aliases cannot be overridden. See the corresponding Define() overload for more information. 0627 RInterface<Proxied> Redefine(std::string_view name, std::string_view expression) 0628 { 0629 constexpr auto where = "Redefine"; 0630 RDFInternal::CheckValidCppVarName(name, where); 0631 RDFInternal::CheckForDefinition(where, name, fColRegister, 0632 GetDataSource() ? GetDataSource()->GetColumnNames() : ColumnNames_t{}); 0633 RDFInternal::CheckForNoVariations(where, name, fColRegister); 0634 0635 auto jittedDefine = RDFInternal::BookDefineJit(name, expression, *fLoopManager, GetDataSource(), fColRegister); 0636 0637 RDFInternal::RColumnRegister newCols(fColRegister); 0638 newCols.AddDefine(std::move(jittedDefine)); 0639 0640 RInterface<Proxied> newInterface(fProxiedPtr, *fLoopManager, std::move(newCols)); 0641 0642 return newInterface; 0643 } 0644 0645 //////////////////////////////////////////////////////////////////////////// 0646 /// \brief In case the value in the given column is missing, provide a default value 0647 /// \tparam T The type of the column 0648 /// \param[in] column Column name where missing values should be replaced by the given default value 0649 /// \param[in] defaultValue Value to provide instead of a missing value 0650 /// \return The node of the graph that will provide a default value 0651 /// 0652 /// This operation is useful in case an entry of the dataset is incomplete, 0653 /// i.e. if one or more of the columns do not have valid values. It does not 0654 /// modify the values of the column, but in case any entry is missing, it 0655 /// will provide the default value to downstream nodes instead. 0656 /// 0657 /// Use cases include: 0658 /// * When processing multiple files, one or more of them is missing a column 0659 /// * In horizontal joining with entry matching, a certain dataset has no 0660 /// match for the current entry. 0661 /// 0662 /// ### Example usage: 0663 /// 0664 /// \code{.cpp} 0665 /// // Assume a dataset with columns [idx, x] matching another dataset with 0666 /// // columns [idx, y]. For idx == 42, the right-hand dataset has no match 0667 /// ROOT::RDataFrame df{dataset}; 0668 /// auto df_default = df.DefaultValueFor("y", 33) 0669 /// .Define("z", [](int x, int y) { return x + y; }, {"x", "y"}); 0670 /// auto colz = df_default.Take<int>("z"); 0671 /// \endcode 0672 /// 0673 /// \code{.py} 0674 /// df = ROOT.RDataFrame(dataset) 0675 /// df_default = df.DefaultValueFor("y", 33).Define("z", "x + y") 0676 /// colz = df_default.Take[int]("z") 0677 /// \endcode 0678 template <typename T> 0679 RInterface<Proxied> DefaultValueFor(std::string_view column, const T &defaultValue) 0680 { 0681 constexpr auto where{"DefaultValueFor"}; 0682 RDFInternal::CheckForNoVariations(where, column, fColRegister); 0683 // For now disable this functionality in case of an empty data source and 0684 // the column name was not defined previously. 0685 if (ROOT::Internal::RDF::GetDataSourceLabel(*this) == "EmptyDS") 0686 RDFInternal::CheckForDefinition(where, column, fColRegister, 0687 GetDataSource() ? GetDataSource()->GetColumnNames() : ColumnNames_t{}); 0688 0689 // Declare return type to the interpreter, for future use by jitted actions 0690 auto retTypeName = RDFInternal::TypeID2TypeName(typeid(T)); 0691 if (retTypeName.empty()) { 0692 // The type is not known to the interpreter. 0693 // We must not error out here, but if/when this column is used in jitted code 0694 const auto demangledType = RDFInternal::DemangleTypeIdName(typeid(T)); 0695 retTypeName = "CLING_UNKNOWN_TYPE_" + demangledType; 0696 } 0697 0698 const auto validColumnNames = ColumnNames_t{column.data()}; 0699 auto newColumn = std::make_shared<ROOT::Internal::RDF::RDefaultValueFor<T>>( 0700 column, retTypeName, defaultValue, validColumnNames, fColRegister, *fLoopManager); 0701 CheckAndFillDSColumns(validColumnNames, TTraits::TypeList<T>{}); 0702 0703 RDFInternal::RColumnRegister newCols(fColRegister); 0704 newCols.AddDefine(std::move(newColumn)); 0705 0706 RInterface<Proxied> newInterface(fProxiedPtr, *fLoopManager, std::move(newCols)); 0707 0708 return newInterface; 0709 } 0710 0711 // clang-format off 0712 //////////////////////////////////////////////////////////////////////////// 0713 /// \brief Define a new column that is updated when the input sample changes. 0714 /// \param[in] name The name of the defined column. 0715 /// \param[in] expression A C++ callable that computes the new value of the defined column. 0716 /// \return the first node of the computation graph for which the new quantity is defined. 0717 /// 0718 /// The signature of the callable passed as second argument should be `T(unsigned int slot, const ROOT::RDF::RSampleInfo &id)` 0719 /// where: 0720 /// - `T` is the type of the defined column 0721 /// - `slot` is a number in the range [0, nThreads) that is different for each processing thread. This can simplify 0722 /// the definition of thread-safe callables if you are interested in using parallel capabilities of RDataFrame. 0723 /// - `id` is an instance of a ROOT::RDF::RSampleInfo object which contains information about the sample which is 0724 /// being processed (see the class docs for more information). 0725 /// 0726 /// DefinePerSample() is useful to e.g. define a quantity that depends on which TTree in which TFile is being 0727 /// processed or to inject a callback into the event loop that is only called when the processing of a new sample 0728 /// starts rather than at every entry. 0729 /// 0730 /// The callable will be invoked once per input TTree or once per multi-thread task, whichever is more often. 0731 /// 0732 /// ### Example usage: 0733 /// ~~~{.cpp} 0734 /// ROOT::RDataFrame df{"mytree", {"sample1.root","sample2.root"}}; 0735 /// df.DefinePerSample("weightbysample", 0736 /// [](unsigned int slot, const ROOT::RDF::RSampleInfo &id) 0737 /// { return id.Contains("sample1") ? 1.0f : 2.0f; }); 0738 /// ~~~ 0739 // clang-format on 0740 // TODO we could SFINAE on F's signature to provide friendlier compilation errors in case of signature mismatch 0741 template <typename F, typename RetType_t = typename TTraits::CallableTraits<F>::ret_type> 0742 RInterface<Proxied> DefinePerSample(std::string_view name, F expression) 0743 { 0744 RDFInternal::CheckValidCppVarName(name, "DefinePerSample"); 0745 RDFInternal::CheckForRedefinition("DefinePerSample", name, fColRegister, 0746 GetDataSource() ? GetDataSource()->GetColumnNames() : ColumnNames_t{}); 0747 0748 auto retTypeName = RDFInternal::TypeID2TypeName(typeid(RetType_t)); 0749 if (retTypeName.empty()) { 0750 // The type is not known to the interpreter. 0751 // We must not error out here, but if/when this column is used in jitted code 0752 const auto demangledType = RDFInternal::DemangleTypeIdName(typeid(RetType_t)); 0753 retTypeName = "CLING_UNKNOWN_TYPE_" + demangledType; 0754 } 0755 0756 auto newColumn = 0757 std::make_shared<RDFDetail::RDefinePerSample<F>>(name, retTypeName, std::move(expression), *fLoopManager); 0758 0759 RDFInternal::RColumnRegister newCols(fColRegister); 0760 newCols.AddDefine(std::move(newColumn)); 0761 RInterface<Proxied> newInterface(fProxiedPtr, *fLoopManager, std::move(newCols)); 0762 return newInterface; 0763 } 0764 0765 // clang-format off 0766 //////////////////////////////////////////////////////////////////////////// 0767 /// \brief Define a new column that is updated when the input sample changes. 0768 /// \param[in] name The name of the defined column. 0769 /// \param[in] expression A valid C++ expression as a string, which will be used to compute the defined value. 0770 /// \return the first node of the computation graph for which the new quantity is defined. 0771 /// 0772 /// The expression is just-in-time compiled and used to produce the column entries. 0773 /// It must be valid C++ syntax and the usage of the special variable names `rdfslot_` and `rdfsampleinfo_` is 0774 /// permitted, where these variables will take the same values as the `slot` and `id` parameters described at the 0775 /// DefinePerSample(std::string_view name, F expression) overload. See the documentation of that overload for more information. 0776 /// 0777 /// ### Example usage: 0778 /// ~~~{.py} 0779 /// df = ROOT.RDataFrame('mytree', ['sample1.root','sample2.root']) 0780 /// df.DefinePerSample('weightbysample', 'rdfsampleinfo_.Contains("sample1") ? 1.0f : 2.0f') 0781 /// ~~~ 0782 /// 0783 /// \note 0784 /// If you have declared some C++ function to the interpreter, the correct syntax to call that function with this 0785 /// overload of DefinePerSample is by calling it explicitly with the special names `rdfslot_` and `rdfsampleinfo_` as 0786 /// input parameters. This is for example the correct way to call this overload when working in PyROOT: 0787 /// ~~~{.py} 0788 /// ROOT.gInterpreter.Declare( 0789 /// """ 0790 /// float weights(unsigned int slot, const ROOT::RDF::RSampleInfo &id){ 0791 /// return id.Contains("sample1") ? 1.0f : 2.0f; 0792 /// } 0793 /// """) 0794 /// df = ROOT.RDataFrame("mytree", ["sample1.root","sample2.root"]) 0795 /// df.DefinePerSample("weightsbysample", "weights(rdfslot_, rdfsampleinfo_)") 0796 /// ~~~ 0797 /// 0798 /// \note 0799 /// Differently from what happens in Define(), the string expression passed to DefinePerSample cannot contain 0800 /// column names other than those mentioned above: the expression is evaluated once before the processing of the 0801 /// sample even starts, so column values are not accessible. 0802 // clang-format on 0803 RInterface<Proxied> DefinePerSample(std::string_view name, std::string_view expression) 0804 { 0805 RDFInternal::CheckValidCppVarName(name, "DefinePerSample"); 0806 // these checks must be done before jitting lest we throw exceptions in jitted code 0807 RDFInternal::CheckForRedefinition("DefinePerSample", name, fColRegister, 0808 GetDataSource() ? GetDataSource()->GetColumnNames() : ColumnNames_t{}); 0809 0810 auto jittedDefine = RDFInternal::BookDefinePerSampleJit(name, expression, *fLoopManager, fColRegister); 0811 0812 RDFInternal::RColumnRegister newCols(fColRegister); 0813 newCols.AddDefine(std::move(jittedDefine)); 0814 0815 RInterface<Proxied> newInterface(fProxiedPtr, *fLoopManager, std::move(newCols)); 0816 0817 return newInterface; 0818 } 0819 0820 /// \brief Register systematic variations for a single existing column using custom variation tags. 0821 /// \param[in] colName name of the column for which varied values are provided. 0822 /// \param[in] expression a callable that evaluates the varied values for the specified columns. The callable can 0823 /// take any column values as input, similarly to what happens during Filter and Define calls. It must 0824 /// return an RVec of varied values, one for each variation tag, in the same order as the tags. 0825 /// \param[in] inputColumns the names of the columns to be passed to the callable. 0826 /// \param[in] variationTags names for each of the varied values, e.g. `"up"` and `"down"`. 0827 /// \param[in] variationName a generic name for this set of varied values, e.g. `"ptvariation"`. 0828 /// 0829 /// Vary provides a natural and flexible syntax to define systematic variations that automatically propagate to 0830 /// Filters, Defines and results. RDataFrame usage of columns with attached variations does not change, but for 0831 /// results that depend on any varied quantity, a map/dictionary of varied results can be produced with 0832 /// ROOT::RDF::Experimental::VariationsFor (see the example below). 0833 /// 0834 /// The dictionary will contain a "nominal" value (accessed with the "nominal" key) for the unchanged result, and 0835 /// values for each of the systematic variations that affected the result (via upstream Filters or via direct or 0836 /// indirect dependencies of the column values on some registered variations). The keys will be a composition of 0837 /// variation names and tags, e.g. "pt:up" and "pt:down" for the example below. 0838 /// 0839 /// In the following example we add up/down variations of pt and fill a histogram with a quantity that depends on pt. 0840 /// We automatically obtain three histograms in output ("nominal", "pt:up" and "pt:down"): 0841 /// ~~~{.cpp} 0842 /// auto nominal_hx = 0843 /// df.Vary("pt", [] (double pt) { return RVecD{pt*0.9, pt*1.1}; }, {"down", "up"}) 0844 /// .Filter("pt > k") 0845 /// .Define("x", someFunc, {"pt"}) 0846 /// .Histo1D("x"); 0847 /// 0848 /// auto hx = ROOT::RDF::Experimental::VariationsFor(nominal_hx); 0849 /// hx["nominal"].Draw(); 0850 /// hx["pt:down"].Draw("SAME"); 0851 /// hx["pt:up"].Draw("SAME"); 0852 /// ~~~ 0853 /// RDataFrame computes all variations as part of a single loop over the data. 0854 /// In particular, this means that I/O and computation of values shared 0855 /// among variations only happen once for all variations. Thus, the event loop 0856 /// run-time typically scales much better than linearly with the number of 0857 /// variations. 0858 /// 0859 /// RDataFrame lazily computes the varied values required to produce the 0860 /// outputs of \ref ROOT::RDF::Experimental::VariationsFor "VariationsFor()". If \ref 0861 /// ROOT::RDF::Experimental::VariationsFor "VariationsFor()" was not called for a result, the computations are only 0862 /// run for the nominal case. 0863 /// 0864 /// See other overloads for examples when variations are added for multiple existing columns, 0865 /// or when the tags are auto-generated instead of being directly defined. 0866 template <typename F> 0867 RInterface<Proxied> Vary(std::string_view colName, F &&expression, const ColumnNames_t &inputColumns, 0868 const std::vector<std::string> &variationTags, std::string_view variationName = "") 0869 { 0870 std::vector<std::string> colNames{{std::string(colName)}}; 0871 const std::string theVariationName{variationName.empty() ? colName : variationName}; 0872 0873 return VaryImpl<true>(std::move(colNames), std::forward<F>(expression), inputColumns, variationTags, 0874 theVariationName); 0875 } 0876 0877 /// \brief Register systematic variations for a single existing column using auto-generated variation tags. 0878 /// \param[in] colName name of the column for which varied values are provided. 0879 /// \param[in] expression a callable that evaluates the varied values for the specified columns. The callable can 0880 /// take any column values as input, similarly to what happens during Filter and Define calls. It must 0881 /// return an RVec of varied values, one for each variation tag, in the same order as the tags. 0882 /// \param[in] inputColumns the names of the columns to be passed to the callable. 0883 /// \param[in] nVariations number of variations returned by the expression. The corresponding tags will be `"0"`, 0884 /// `"1"`, etc. 0885 /// \param[in] variationName a generic name for this set of varied values, e.g. `"ptvariation"`. 0886 /// colName is used if none is provided. 0887 /// 0888 /// This overload of Vary takes an nVariations parameter instead of a list of tag names. 0889 /// The varied results will be accessible via the keys of the dictionary with the form `variationName:N` where `N` 0890 /// is the corresponding sequential tag starting at 0 and going up to `nVariations - 1`. 0891 /// 0892 /// Example usage: 0893 /// ~~~{.cpp} 0894 /// auto nominal_hx = 0895 /// df.Vary("pt", [] (double pt) { return RVecD{pt*0.9, pt*1.1}; }, 2) 0896 /// .Histo1D("x"); 0897 /// 0898 /// auto hx = ROOT::RDF::Experimental::VariationsFor(nominal_hx); 0899 /// hx["nominal"].Draw(); 0900 /// hx["x:0"].Draw("SAME"); 0901 /// hx["x:1"].Draw("SAME"); 0902 /// ~~~ 0903 /// 0904 /// \note See also This Vary() overload for more information. 0905 template <typename F> 0906 RInterface<Proxied> Vary(std::string_view colName, F &&expression, const ColumnNames_t &inputColumns, 0907 std::size_t nVariations, std::string_view variationName = "") 0908 { 0909 R__ASSERT(nVariations > 0 && "Must have at least one variation."); 0910 0911 std::vector<std::string> variationTags; 0912 variationTags.reserve(nVariations); 0913 for (std::size_t i = 0u; i < nVariations; ++i) 0914 variationTags.emplace_back(std::to_string(i)); 0915 0916 const std::string theVariationName{variationName.empty() ? colName : variationName}; 0917 0918 return Vary(colName, std::forward<F>(expression), inputColumns, std::move(variationTags), theVariationName); 0919 } 0920 0921 /// \brief Register systematic variations for multiple existing columns using custom variation tags. 0922 /// \param[in] colNames set of names of the columns for which varied values are provided. 0923 /// \param[in] expression a callable that evaluates the varied values for the specified columns. The callable can 0924 /// take any column values as input, similarly to what happens during Filter and Define calls. It must 0925 /// return an RVec of varied values, one for each variation tag, in the same order as the tags. 0926 /// \param[in] inputColumns the names of the columns to be passed to the callable. 0927 /// \param[in] variationTags names for each of the varied values, e.g. `"up"` and `"down"`. 0928 /// \param[in] variationName a generic name for this set of varied values, e.g. `"ptvariation"` 0929 /// 0930 /// This overload of Vary takes a list of column names as first argument and 0931 /// requires that the expression returns an RVec of RVecs of values: one inner RVec for the variations of each 0932 /// affected column. The `variationTags` are defined as `{"down", "up"}`. 0933 /// 0934 /// Example usage: 0935 /// ~~~{.cpp} 0936 /// // produce variations "ptAndEta:down" and "ptAndEta:up" 0937 /// auto nominal_hx = 0938 /// df.Vary({"pt", "eta"}, // the columns that will vary simultaneously 0939 /// [](double pt, double eta) { return RVec<RVecF>{{pt*0.9, pt*1.1}, {eta*0.9, eta*1.1}}; }, 0940 /// {"pt", "eta"}, // inputs to the Vary expression, independent of what columns are varied 0941 /// {"down", "up"}, // variation tags 0942 /// "ptAndEta") // variation name 0943 /// .Histo1D("pt", "eta"); 0944 /// 0945 /// auto hx = ROOT::RDF::Experimental::VariationsFor(nominal_hx); 0946 /// hx["nominal"].Draw(); 0947 /// hx["ptAndEta:down"].Draw("SAME"); 0948 /// hx["ptAndEta:up"].Draw("SAME"); 0949 /// ~~~ 0950 /// 0951 /// \note See also This Vary() overload for more information. 0952 0953 template <typename F> 0954 RInterface<Proxied> Vary(const std::vector<std::string> &colNames, F &&expression, const ColumnNames_t &inputColumns, 0955 const std::vector<std::string> &variationTags, std::string_view variationName) 0956 { 0957 return VaryImpl<false>(colNames, std::forward<F>(expression), inputColumns, variationTags, variationName); 0958 } 0959 0960 /// \brief Register systematic variations for multiple existing columns using custom variation tags. 0961 /// \param[in] colNames set of names of the columns for which varied values are provided. 0962 /// \param[in] expression a callable that evaluates the varied values for the specified columns. The callable can 0963 /// take any column values as input, similarly to what happens during Filter and Define calls. It must 0964 /// return an RVec of varied values, one for each variation tag, in the same order as the tags. 0965 /// \param[in] inputColumns the names of the columns to be passed to the callable. 0966 /// \param[in] variationTags names for each of the varied values, e.g. `"up"` and `"down"`. 0967 /// \param[in] variationName a generic name for this set of varied values, e.g. `"ptvariation"`. 0968 /// colName is used if none is provided. 0969 /// 0970 /// \note This overload ensures that the ambiguity between C++20 string, vector<string> construction from init list 0971 /// is avoided. 0972 /// 0973 /// \note See also This Vary() overload for more information. 0974 template <typename F> 0975 RInterface<Proxied> 0976 Vary(std::initializer_list<std::string> colNames, F &&expression, const ColumnNames_t &inputColumns, 0977 const std::vector<std::string> &variationTags, std::string_view variationName) 0978 { 0979 return Vary(std::vector<std::string>(colNames), std::forward<F>(expression), inputColumns, variationTags, variationName); 0980 } 0981 0982 /// \brief Register systematic variations for multiple existing columns using auto-generated tags. 0983 /// \param[in] colNames set of names of the columns for which varied values are provided. 0984 /// \param[in] expression a callable that evaluates the varied values for the specified columns. The callable can 0985 /// take any column values as input, similarly to what happens during Filter and Define calls. It must 0986 /// return an RVec of varied values, one for each variation tag, in the same order as the tags. 0987 /// \param[in] inputColumns the names of the columns to be passed to the callable. 0988 /// \param[in] nVariations number of variations returned by the expression. The corresponding tags will be `"0"`, 0989 /// `"1"`, etc. 0990 /// \param[in] variationName a generic name for this set of varied values, e.g. `"ptvariation"`. 0991 /// colName is used if none is provided. 0992 /// 0993 /// This overload of Vary takes a list of column names as first argument. 0994 /// It takes an `nVariations` parameter instead of a list of tag names (`variationTags`). Tag names 0995 /// will be auto-generated as the sequence 0...``nVariations-1``. 0996 /// 0997 /// Example usage: 0998 /// ~~~{.cpp} 0999 /// auto nominal_hx = 1000 /// df.Vary({"pt", "eta"}, // the columns that will vary simultaneously 1001 /// [](double pt, double eta) { return RVec<RVecF>{{pt*0.9, pt*1.1}, {eta*0.9, eta*1.1}}; }, 1002 /// {"pt", "eta"}, // inputs to the Vary expression, independent of what columns are varied 1003 /// 2, // auto-generated variation tags 1004 /// "ptAndEta") // variation name 1005 /// .Histo1D("pt", "eta"); 1006 /// 1007 /// auto hx = ROOT::RDF::Experimental::VariationsFor(nominal_hx); 1008 /// hx["nominal"].Draw(); 1009 /// hx["ptAndEta:0"].Draw("SAME"); 1010 /// hx["ptAndEta:1"].Draw("SAME"); 1011 /// ~~~ 1012 /// 1013 /// \note See also This Vary() overload for more information. 1014 template <typename F> 1015 RInterface<Proxied> Vary(const std::vector<std::string> &colNames, F &&expression, const ColumnNames_t &inputColumns, 1016 std::size_t nVariations, std::string_view variationName) 1017 { 1018 R__ASSERT(nVariations > 0 && "Must have at least one variation."); 1019 1020 std::vector<std::string> variationTags; 1021 variationTags.reserve(nVariations); 1022 for (std::size_t i = 0u; i < nVariations; ++i) 1023 variationTags.emplace_back(std::to_string(i)); 1024 1025 return Vary(colNames, std::forward<F>(expression), inputColumns, std::move(variationTags), variationName); 1026 } 1027 1028 /// \brief Register systematic variations for for multiple existing columns using custom variation tags. 1029 /// \param[in] colNames set of names of the columns for which varied values are provided. 1030 /// \param[in] expression a callable that evaluates the varied values for the specified columns. The callable can 1031 /// take any column values as input, similarly to what happens during Filter and Define calls. It must 1032 /// return an RVec of varied values, one for each variation tag, in the same order as the tags. 1033 /// \param[in] inputColumns the names of the columns to be passed to the callable. 1034 /// \param[in] inputColumns the names of the columns to be passed to the callable. 1035 /// \param[in] nVariations number of variations returned by the expression. The corresponding tags will be `"0"`, 1036 /// `"1"`, etc. 1037 /// \param[in] variationName a generic name for this set of varied values, e.g. `"ptvariation"`. 1038 /// colName is used if none is provided. 1039 /// 1040 /// \note This overload ensures that the ambiguity between C++20 string, vector<string> construction from init list 1041 /// is avoided. 1042 /// 1043 /// \note See also This Vary() overload for more information. 1044 template <typename F> 1045 RInterface<Proxied> Vary(std::initializer_list<std::string> colNames, F &&expression, 1046 const ColumnNames_t &inputColumns, std::size_t nVariations, std::string_view variationName) 1047 { 1048 return Vary(std::vector<std::string>(colNames), std::forward<F>(expression), inputColumns, nVariations, variationName); 1049 } 1050 1051 /// \brief Register systematic variations for a single existing column using custom variation tags. 1052 /// \param[in] colName name of the column for which varied values are provided. 1053 /// \param[in] expression a string containing valid C++ code that evaluates to an RVec containing the varied 1054 /// values for the specified column. 1055 /// \param[in] variationTags names for each of the varied values, e.g. `"up"` and `"down"`. 1056 /// \param[in] variationName a generic name for this set of varied values, e.g. `"ptvariation"`. 1057 /// colName is used if none is provided. 1058 /// 1059 /// This overload adds the possibility for the expression used to evaluate the varied values to be just-in-time 1060 /// compiled. The example below shows how Vary() is used while dealing with a single column. The variation tags are 1061 /// defined as `{"down", "up"}`. 1062 /// ~~~{.cpp} 1063 /// auto nominal_hx = 1064 /// df.Vary("pt", "ROOT::RVecD{pt*0.9, pt*1.1}", {"down", "up"}) 1065 /// .Filter("pt > k") 1066 /// .Define("x", someFunc, {"pt"}) 1067 /// .Histo1D("x"); 1068 /// 1069 /// auto hx = ROOT::RDF::Experimental::VariationsFor(nominal_hx); 1070 /// hx["nominal"].Draw(); 1071 /// hx["pt:down"].Draw("SAME"); 1072 /// hx["pt:up"].Draw("SAME"); 1073 /// ~~~ 1074 /// 1075 /// ## Short-hand expression syntax 1076 /// 1077 /// For convenience, when a C++ expression is passed to Vary, the return type can be omitted if the string begins 1078 /// with '{' and ends with '}' (whitespace, tab and newline characters are excluded from the search). This means that 1079 /// the following is equivalent to the example above: 1080 /// 1081 /// ~~~{.cpp} 1082 /// auto nominal_hx = 1083 /// df.Vary("pt", "{pt*0.9, pt*1.1}", {"down", "up"}) 1084 /// // Same as above 1085 /// ~~~ 1086 /// 1087 /// \note See also This Vary() overload for more information. 1088 RInterface<Proxied> Vary(std::string_view colName, std::string_view expression, 1089 const std::vector<std::string> &variationTags, std::string_view variationName = "") 1090 { 1091 std::vector<std::string> colNames{{std::string(colName)}}; 1092 const std::string theVariationName{variationName.empty() ? colName : variationName}; 1093 1094 return JittedVaryImpl(colNames, expression, variationTags, theVariationName, /*isSingleColumn=*/true); 1095 } 1096 1097 /// \brief Register systematic variations for a single existing column using auto-generated variation tags. 1098 /// \param[in] colName name of the column for which varied values are provided. 1099 /// \param[in] expression a string containing valid C++ code that evaluates to an RVec containing the varied 1100 /// values for the specified column. 1101 /// \param[in] nVariations number of variations returned by the expression. The corresponding tags will be `"0"`, 1102 /// `"1"`, etc. 1103 /// \param[in] variationName a generic name for this set of varied values, e.g. `"ptvariation"`. 1104 /// colName is used if none is provided. 1105 /// 1106 /// This overload adds the possibility for the expression used to evaluate the varied values to be a just-in-time 1107 /// compiled. The example below shows how Vary() is used while dealing with a single column. The variation tags are 1108 /// auto-generated. 1109 /// ~~~{.cpp} 1110 /// auto nominal_hx = 1111 /// df.Vary("pt", "ROOT::RVecD{pt*0.9, pt*1.1}", 2) 1112 /// .Histo1D("pt"); 1113 /// 1114 /// auto hx = ROOT::RDF::Experimental::VariationsFor(nominal_hx); 1115 /// hx["nominal"].Draw(); 1116 /// hx["pt:0"].Draw("SAME"); 1117 /// hx["pt:1"].Draw("SAME"); 1118 /// ~~~ 1119 /// 1120 /// ## Short-hand expression syntax 1121 /// 1122 /// For convenience, when a C++ expression is passed to Vary, the return type can be omitted if the string begins 1123 /// with '{' and ends with '}' (whitespace, tab and newline characters are excluded from the search). This means that 1124 /// the following is equivalent to the example above: 1125 /// 1126 /// ~~~{.cpp} 1127 /// auto nominal_hx = 1128 /// df.Vary("pt", "{pt*0.9, pt*1.1}", 2) 1129 /// // Same as above 1130 /// ~~~ 1131 /// 1132 /// \note See also This Vary() overload for more information. 1133 RInterface<Proxied> Vary(std::string_view colName, std::string_view expression, std::size_t nVariations, 1134 std::string_view variationName = "") 1135 { 1136 std::vector<std::string> variationTags; 1137 variationTags.reserve(nVariations); 1138 for (std::size_t i = 0u; i < nVariations; ++i) 1139 variationTags.emplace_back(std::to_string(i)); 1140 1141 return Vary(colName, expression, std::move(variationTags), variationName); 1142 } 1143 1144 /// \brief Register systematic variations for multiple existing columns using auto-generated variation tags. 1145 /// \param[in] colNames set of names of the columns for which varied values are provided. 1146 /// \param[in] expression a string containing valid C++ code that evaluates to an RVec or RVecs containing the varied 1147 /// values for the specified columns. 1148 /// \param[in] nVariations number of variations returned by the expression. The corresponding tags will be `"0"`, 1149 /// `"1"`, etc. 1150 /// \param[in] variationName a generic name for this set of varied values, e.g. `"ptvariation"`. 1151 /// 1152 /// This overload adds the possibility for the expression used to evaluate the varied values to be just-in-time 1153 /// compiled. It takes an nVariations parameter instead of a list of tag names. 1154 /// The varied results will be accessible via the keys of the dictionary with the form `variationName:N` where `N` 1155 /// is the corresponding sequential tag starting at 0 and going up to `nVariations - 1`. 1156 /// The example below shows how Vary() is used while dealing with multiple columns. 1157 /// 1158 /// ~~~{.cpp} 1159 /// auto nominal_hx = 1160 /// df.Vary({"x", "y"}, "ROOT::RVec<ROOT::RVecD>{{x*0.9, x*1.1}, {y*0.9, y*1.1}}", 2, "xy") 1161 /// .Histo1D("x", "y"); 1162 /// 1163 /// auto hx = ROOT::RDF::Experimental::VariationsFor(nominal_hx); 1164 /// hx["nominal"].Draw(); 1165 /// hx["xy:0"].Draw("SAME"); 1166 /// hx["xy:1"].Draw("SAME"); 1167 /// ~~~ 1168 /// 1169 /// ## Short-hand expression syntax 1170 /// 1171 /// For convenience, when a C++ expression is passed to Vary, the return type can be omitted if the string begins 1172 /// with '{' and ends with '}' (whitespace, tab and newline characters are excluded from the search). This means that 1173 /// the following is equivalent to the example above: 1174 /// 1175 /// ~~~{.cpp} 1176 /// auto nominal_hx = 1177 /// df.Vary("pt", "{{x*0.9, x*1.1}, {y*0.9, y*1.1}}", 2, "xy") 1178 /// // Same as above 1179 /// ~~~ 1180 /// 1181 /// or also: 1182 /// 1183 /// ~~~{.cpp} 1184 /// auto nominal_hx = 1185 /// df.Vary("pt", R"( 1186 /// { 1187 /// {x*0.9, x*1.1}, // x variations 1188 /// {y*0.9, y*1.1} // y variations 1189 /// } 1190 /// )", 2, "xy") 1191 /// // Same as above 1192 /// ~~~ 1193 /// 1194 /// \note See also This Vary() overload for more information. 1195 RInterface<Proxied> Vary(const std::vector<std::string> &colNames, std::string_view expression, 1196 std::size_t nVariations, std::string_view variationName) 1197 { 1198 std::vector<std::string> variationTags; 1199 variationTags.reserve(nVariations); 1200 for (std::size_t i = 0u; i < nVariations; ++i) 1201 variationTags.emplace_back(std::to_string(i)); 1202 1203 return Vary(colNames, expression, std::move(variationTags), variationName); 1204 } 1205 1206 /// \brief Register systematic variations for multiple existing columns using auto-generated variation tags. 1207 /// \param[in] colNames set of names of the columns for which varied values are provided. 1208 /// \param[in] expression a string containing valid C++ code that evaluates to an RVec containing the varied 1209 /// values for the specified column. 1210 /// \param[in] nVariations number of variations returned by the expression. The corresponding tags will be `"0"`, 1211 /// `"1"`, etc. 1212 /// \param[in] variationName a generic name for this set of varied values, e.g. `"ptvariation"`. 1213 /// colName is used if none is provided. 1214 /// 1215 /// \note This overload ensures that the ambiguity between C++20 string, vector<string> construction from init list 1216 /// is avoided. 1217 /// 1218 /// \note See also This Vary() overload for more information. 1219 RInterface<Proxied> Vary(std::initializer_list<std::string> colNames, std::string_view expression, 1220 std::size_t nVariations, std::string_view variationName) 1221 { 1222 return Vary(std::vector<std::string>(colNames), expression, nVariations, variationName); 1223 } 1224 1225 /// \brief Register systematic variations for multiple existing columns using custom variation tags. 1226 /// \param[in] colNames set of names of the columns for which varied values are provided. 1227 /// \param[in] expression a string containing valid C++ code that evaluates to an RVec or RVecs containing the varied 1228 /// values for the specified columns. 1229 /// \param[in] variationTags names for each of the varied values, e.g. `"up"` and `"down"`. 1230 /// \param[in] variationName a generic name for this set of varied values, e.g. `"ptvariation"`. 1231 /// 1232 /// This overload adds the possibility for the expression used to evaluate the varied values to be just-in-time 1233 /// compiled. The example below shows how Vary() is used while dealing with multiple columns. The tags are defined as 1234 /// `{"down", "up"}`. 1235 /// ~~~{.cpp} 1236 /// auto nominal_hx = 1237 /// df.Vary({"x", "y"}, "ROOT::RVec<ROOT::RVecD>{{x*0.9, x*1.1}, {y*0.9, y*1.1}}", {"down", "up"}, "xy") 1238 /// .Histo1D("x", "y"); 1239 /// 1240 /// auto hx = ROOT::RDF::Experimental::VariationsFor(nominal_hx); 1241 /// hx["nominal"].Draw(); 1242 /// hx["xy:down"].Draw("SAME"); 1243 /// hx["xy:up"].Draw("SAME"); 1244 /// ~~~ 1245 /// 1246 /// ## Short-hand expression syntax 1247 /// 1248 /// For convenience, when a C++ expression is passed to Vary, the return type can be omitted if the string begins 1249 /// with '{' and ends with '}' (whitespace, tab and newline characters are excluded from the search). This means that 1250 /// the following is equivalent to the example above: 1251 /// 1252 /// ~~~{.cpp} 1253 /// auto nominal_hx = 1254 /// df.Vary("pt", "{{x*0.9, x*1.1}, {y*0.9, y*1.1}}", {"down", "up"}, "xy") 1255 /// // Same as above 1256 /// ~~~ 1257 /// 1258 /// or also: 1259 /// 1260 /// ~~~{.cpp} 1261 /// auto nominal_hx = 1262 /// df.Vary("pt", R"( 1263 /// { 1264 /// {x*0.9, x*1.1}, // x variations 1265 /// {y*0.9, y*1.1} // y variations 1266 /// } 1267 /// )", {"down", "up"}, "xy") 1268 /// // Same as above 1269 /// ~~~ 1270 /// 1271 /// \note See also This Vary() overload for more information. 1272 RInterface<Proxied> Vary(const std::vector<std::string> &colNames, std::string_view expression, 1273 const std::vector<std::string> &variationTags, std::string_view variationName) 1274 { 1275 return JittedVaryImpl(colNames, expression, variationTags, variationName, /*isSingleColumn=*/false); 1276 } 1277 1278 //////////////////////////////////////////////////////////////////////////// 1279 /// \brief Allow to refer to a column with a different name. 1280 /// \param[in] alias name of the column alias 1281 /// \param[in] columnName of the column to be aliased 1282 /// \return the first node of the computation graph for which the alias is available. 1283 /// 1284 /// Aliasing an alias is supported. 1285 /// 1286 /// ### Example usage: 1287 /// ~~~{.cpp} 1288 /// auto df_with_alias = df.Alias("simple_name", "very_long&complex_name!!!"); 1289 /// ~~~ 1290 RInterface<Proxied> Alias(std::string_view alias, std::string_view columnName) 1291 { 1292 // The symmetry with Define is clear. We want to: 1293 // - Create globally the alias and return this very node, unchanged 1294 // - Make aliases accessible based on chains and not globally 1295 1296 // Helper to find out if a name is a column 1297 auto &dsColumnNames = GetDataSource() ? GetDataSource()->GetColumnNames() : ColumnNames_t{}; 1298 1299 constexpr auto where = "Alias"; 1300 RDFInternal::CheckValidCppVarName(alias, where); 1301 // If the alias name is a column name, there is a problem 1302 RDFInternal::CheckForRedefinition(where, alias, fColRegister, dsColumnNames); 1303 1304 const auto validColumnName = GetValidatedColumnNames(1, {std::string(columnName)})[0]; 1305 1306 RDFInternal::RColumnRegister newCols(fColRegister); 1307 newCols.AddAlias(alias, validColumnName); 1308 1309 RInterface<Proxied> newInterface(fProxiedPtr, *fLoopManager, std::move(newCols)); 1310 1311 return newInterface; 1312 } 1313 1314 template <typename... ColumnTypes> 1315 [[deprecated("Snapshot is not any more a template. You can safely remove the template parameters.")]] 1316 RResultPtr<RInterface<RLoopManager>> 1317 Snapshot(std::string_view treename, std::string_view filename, const ColumnNames_t &columnList, 1318 const RSnapshotOptions &options = RSnapshotOptions()) 1319 { 1320 return Snapshot(treename, filename, columnList, options); 1321 } 1322 1323 //////////////////////////////////////////////////////////////////////////// 1324 /// \brief Save selected columns to disk, in a new TTree or RNTuple `treename` in file `filename`. 1325 /// \param[in] treename The name of the output TTree or RNTuple. 1326 /// \param[in] filename The name of the output TFile. 1327 /// \param[in] columnList The list of names of the columns/branches/fields to be written. 1328 /// \param[in] options RSnapshotOptions struct with extra options to pass to TFile and TTree/RNTuple. 1329 /// \return a `RDataFrame` that wraps the snapshotted dataset. 1330 /// 1331 /// This function returns a `RDataFrame` built with the output TTree or RNTuple as a source. 1332 /// The types of the columns are automatically inferred and do not need to be specified. 1333 /// 1334 /// Support for writing of nested branches/fields is limited (although RDataFrame is able to read them) and dot ('.') 1335 /// characters in input column names will be replaced by underscores ('_') in the branches produced by Snapshot. 1336 /// When writing a variable size array through Snapshot, it is required that the column indicating its size is also 1337 /// written out and it appears before the array in the columnList. 1338 /// 1339 /// By default, in case of TTree, TChain or RNTuple inputs, Snapshot will try to write out all top-level branches. 1340 /// For other types of inputs, all columns returned by GetColumnNames() will be written out. Systematic variations of 1341 /// columns will be included if the corresponding flag is set in RSnapshotOptions. See \ref snapshot-with-variations 1342 /// "Snapshot with Variations" for more details. If friend trees or chains are present, by default all friend 1343 /// top-level branches that have names that do not collide with names of branches in the main TTree/TChain will be 1344 /// written out. Since v6.24, Snapshot will also write out friend branches with the same names of branches in the 1345 /// main TTree/TChain with names of the form 1346 /// `<friendname>_<branchname>` in order to differentiate them from the branches in the main tree/chain. 1347 /// 1348 /// ### Writing to a sub-directory 1349 /// 1350 /// Snapshot supports writing the TTree or RNTuple in a sub-directory inside the TFile. It is sufficient to specify 1351 /// the directory path as part of the TTree or RNTuple name, e.g. `df.Snapshot("subdir/t", "f.root")` writes TTree 1352 /// `t` in the sub-directory `subdir` of file `f.root` (creating file and sub-directory as needed). 1353 /// 1354 /// \attention In multi-thread runs (i.e. when EnableImplicitMT() has been called) threads will loop over clusters of 1355 /// entries in an undefined order, so Snapshot will produce outputs in which (clusters of) entries will be shuffled 1356 /// with respect to the input TTree. Using such "shuffled" TTrees as friends of the original trees would result in 1357 /// wrong associations between entries in the main TTree and entries in the "shuffled" friend. Since v6.22, ROOT will 1358 /// error out if such a "shuffled" TTree is used in a friendship. 1359 /// 1360 /// \note In case no events are written out (e.g. because no event passes all filters), Snapshot will still write the 1361 /// requested output TTree or RNTuple to the file, with all the branches requested to preserve the dataset schema. 1362 /// 1363 /// \note Snapshot will refuse to process columns with names of the form `#columnname`. These are special columns 1364 /// made available by some data sources (e.g. RNTupleDS) that represent the size of column `columnname`, and are 1365 /// not meant to be written out with that name (which is not a valid C++ variable name). Instead, go through an 1366 /// Alias(): `df.Alias("nbar", "#bar").Snapshot(..., {"nbar"})`. 1367 /// 1368 /// ### Example invocations: 1369 /// 1370 /// ~~~{.cpp} 1371 /// // No need to specify column types, they are automatically deduced thanks 1372 /// // to information coming from the data source 1373 /// df.Snapshot("outputTree", "outputFile.root", {"x", "y"}); 1374 /// ~~~ 1375 /// 1376 /// To book a Snapshot without triggering the event loop, one needs to set the appropriate flag in 1377 /// `RSnapshotOptions`: 1378 /// ~~~{.cpp} 1379 /// RSnapshotOptions opts; 1380 /// opts.fLazy = true; 1381 /// df.Snapshot("outputTree", "outputFile.root", {"x"}, opts); 1382 /// ~~~ 1383 /// 1384 /// To snapshot to the RNTuple data format, the `fOutputFormat` option in `RSnapshotOptions` needs to be set 1385 /// accordingly: 1386 /// ~~~{.cpp} 1387 /// RSnapshotOptions opts; 1388 /// opts.fOutputFormat = ROOT::RDF::ESnapshotOutputFormat::kRNTuple; 1389 /// df.Snapshot("outputNTuple", "outputFile.root", {"x"}, opts); 1390 /// ~~~ 1391 /// 1392 /// Snapshot systematic variations resulting from a Vary() call (see details \ref snapshot-with-variations "here"): 1393 /// ~~~{.cpp} 1394 /// RSnapshotOptions opts; 1395 /// opts.fIncludeVariations = true; 1396 /// df.Snapshot("outputTree", "outputFile.root", {"x"}, opts); 1397 /// ~~~ 1398 RResultPtr<RInterface<RLoopManager>> Snapshot(std::string_view treename, std::string_view filename, 1399 const ColumnNames_t &columnList, 1400 const RSnapshotOptions &options = RSnapshotOptions()) 1401 { 1402 // like columnList but with `#var` columns removed 1403 auto colListNoPoundSizes = RDFInternal::FilterArraySizeColNames(columnList, "Snapshot"); 1404 // like columnListWithoutSizeColumns but with aliases resolved 1405 auto colListNoAliases = GetValidatedColumnNames(colListNoPoundSizes.size(), colListNoPoundSizes); 1406 RDFInternal::CheckForDuplicateSnapshotColumns(colListNoAliases); 1407 // like validCols but with missing size branches required by array branches added in the right positions 1408 const auto pairOfColumnLists = 1409 RDFInternal::AddSizeBranches(GetDataSource(), std::move(colListNoAliases), std::move(colListNoPoundSizes)); 1410 const auto &colListNoAliasesWithSizeBranches = pairOfColumnLists.first; 1411 const auto &colListWithAliasesAndSizeBranches = pairOfColumnLists.second; 1412 1413 const auto fullTreeName = treename; 1414 const auto parsedTreePath = RDFInternal::ParseTreePath(fullTreeName); 1415 treename = parsedTreePath.fTreeName; 1416 const auto &dirname = parsedTreePath.fDirName; 1417 1418 ::TDirectory::TContext ctxt; 1419 1420 RResultPtr<RInterface<RLoopManager>> resPtr; 1421 1422 auto retrieveTypeID = [](const std::string &colName, const std::string &colTypeName, 1423 bool isRNTuple = false) -> const std::type_info * { 1424 try { 1425 return &ROOT::Internal::RDF::TypeName2TypeID(colTypeName); 1426 } catch (const std::runtime_error &err) { 1427 if (isRNTuple) 1428 return &typeid(ROOT::Internal::RDF::UseNativeDataType); 1429 1430 if (std::string(err.what()).find("Cannot extract type_info of type") != std::string::npos) { 1431 // We could not find RTTI for this column, thus we cannot write it out at the moment. 1432 std::string trueTypeName{colTypeName}; 1433 if (colTypeName.rfind("CLING_UNKNOWN_TYPE", 0) == 0) 1434 trueTypeName = colTypeName.substr(19); 1435 std::string msg{"No runtime type information is available for column \"" + colName + 1436 "\" with type name \"" + trueTypeName + 1437 "\". Thus, it cannot be written to disk with Snapshot. Make sure to generate and load " 1438 "ROOT dictionaries for the type of this column."}; 1439 1440 throw std::runtime_error(msg); 1441 } else { 1442 throw; 1443 } 1444 } 1445 }; 1446 1447 RDFInternal::CheckSnapshotOptionsFormatCompatibility(options); 1448 1449 if (options.fOutputFormat == ESnapshotOutputFormat::kRNTuple) { 1450 // The data source of the RNTuple resulting from the Snapshot action does not exist yet here, so we create one 1451 // without a data source for now, and set it once the actual data source can be created (i.e., after 1452 // writing the RNTuple). 1453 auto newRDF = std::make_shared<RInterface<RLoopManager>>(std::make_shared<RLoopManager>(colListNoPoundSizes)); 1454 1455 auto snapHelperArgs = std::make_shared<RDFInternal::SnapshotHelperArgs>(RDFInternal::SnapshotHelperArgs{ 1456 std::string(filename), std::string(dirname), std::string(treename), colListWithAliasesAndSizeBranches, 1457 options, newRDF->GetLoopManager(), GetLoopManager(), true /* fToNTuple */, /*fIncludeVariations=*/false}); 1458 1459 auto &&nColumns = colListNoAliasesWithSizeBranches.size(); 1460 const auto validColumnNames = GetValidatedColumnNames(nColumns, colListNoAliasesWithSizeBranches); 1461 1462 const auto nSlots = fLoopManager->GetNSlots(); 1463 std::vector<const std::type_info *> colTypeIDs; 1464 colTypeIDs.reserve(nColumns); 1465 for (decltype(nColumns) i{}; i < nColumns; i++) { 1466 const auto &colName = validColumnNames[i]; 1467 const auto colTypeName = ROOT::Internal::RDF::ColumnName2ColumnTypeName( 1468 colName, /*tree*/ nullptr, GetDataSource(), fColRegister.GetDefine(colName), options.fVector2RVec); 1469 const std::type_info *colTypeID = retrieveTypeID(colName, colTypeName, /*isRNTuple*/ true); 1470 colTypeIDs.push_back(colTypeID); 1471 } 1472 // Crucial e.g. if the column names do not correspond to already-available column readers created by the data 1473 // source 1474 CheckAndFillDSColumns(validColumnNames, colTypeIDs); 1475 1476 auto action = 1477 RDFInternal::BuildAction(validColumnNames, snapHelperArgs, nSlots, fProxiedPtr, fColRegister, colTypeIDs); 1478 resPtr = MakeResultPtr(newRDF, *GetLoopManager(), std::move(action)); 1479 } else { 1480 if (RDFInternal::GetDataSourceLabel(*this) == "RNTupleDS" && 1481 options.fOutputFormat == ESnapshotOutputFormat::kDefault) { 1482 Warning("Snapshot", 1483 "The default Snapshot output data format is TTree, but the input data format is RNTuple. If you " 1484 "want to Snapshot to RNTuple or suppress this warning, set the appropriate fOutputFormat option in " 1485 "RSnapshotOptions. Note that this current default behaviour might change in the future."); 1486 } 1487 1488 // We create an RLoopManager without a data source. This needs to be initialised when the output TTree dataset 1489 // has actually been created and written to TFile, i.e. at the end of the Snapshot execution. 1490 auto newRDF = std::make_shared<RInterface<RLoopManager>>( 1491 std::make_shared<RLoopManager>(colListNoAliasesWithSizeBranches)); 1492 1493 auto snapHelperArgs = std::make_shared<RDFInternal::SnapshotHelperArgs>(RDFInternal::SnapshotHelperArgs{ 1494 std::string(filename), std::string(dirname), std::string(treename), colListWithAliasesAndSizeBranches, 1495 options, newRDF->GetLoopManager(), GetLoopManager(), false /* fToRNTuple */, options.fIncludeVariations}); 1496 1497 auto &&nColumns = colListNoAliasesWithSizeBranches.size(); 1498 const auto validColumnNames = GetValidatedColumnNames(nColumns, colListNoAliasesWithSizeBranches); 1499 1500 const auto nSlots = fLoopManager->GetNSlots(); 1501 std::vector<const std::type_info *> colTypeIDs; 1502 colTypeIDs.reserve(nColumns); 1503 for (decltype(nColumns) i{}; i < nColumns; i++) { 1504 const auto &colName = validColumnNames[i]; 1505 const auto colTypeName = ROOT::Internal::RDF::ColumnName2ColumnTypeName( 1506 colName, /*tree*/ nullptr, GetDataSource(), fColRegister.GetDefine(colName), options.fVector2RVec); 1507 const std::type_info *colTypeID = retrieveTypeID(colName, colTypeName); 1508 colTypeIDs.push_back(colTypeID); 1509 } 1510 // Crucial e.g. if the column names do not correspond to already-available column readers created by the data 1511 // source 1512 CheckAndFillDSColumns(validColumnNames, colTypeIDs); 1513 1514 auto action = 1515 RDFInternal::BuildAction(validColumnNames, snapHelperArgs, nSlots, fProxiedPtr, fColRegister, colTypeIDs); 1516 resPtr = MakeResultPtr(newRDF, *GetLoopManager(), std::move(action)); 1517 } 1518 1519 if (!options.fLazy) 1520 *resPtr; 1521 return resPtr; 1522 } 1523 1524 // clang-format off 1525 //////////////////////////////////////////////////////////////////////////// 1526 /// \brief Save selected columns to disk, in a new TTree or RNTuple `treename` in file `filename`. 1527 /// \param[in] treename The name of the output TTree or RNTuple. 1528 /// \param[in] filename The name of the output TFile. 1529 /// \param[in] columnNameRegexp The regular expression to match the column names to be selected. The presence of a '^' and a '$' at the end of the string is implicitly assumed if they are not specified. The dialect supported is PCRE via the TPRegexp class. An empty string signals the selection of all columns. 1530 /// \param[in] options RSnapshotOptions struct with extra options to pass to TFile and TTree/RNTuple 1531 /// \return a `RDataFrame` that wraps the snapshotted dataset. 1532 /// 1533 /// This function returns a `RDataFrame` built with the output TTree or RNTuple as a source. 1534 /// The types of the columns are automatically inferred and do not need to be specified. 1535 /// 1536 /// See Snapshot(std::string_view, std::string_view, const ColumnNames_t&, const RSnapshotOptions &) for a more complete description and example usages. 1537 RResultPtr<RInterface<RLoopManager>> Snapshot(std::string_view treename, std::string_view filename, 1538 std::string_view columnNameRegexp = "", 1539 const RSnapshotOptions &options = RSnapshotOptions()) 1540 { 1541 const auto definedColumns = fColRegister.GenerateColumnNames(); 1542 1543 const auto dsColumns = GetDataSource() ? ROOT::Internal::RDF::GetTopLevelFieldNames(*GetDataSource()) : ColumnNames_t{}; 1544 // Ignore R_rdf_sizeof_* columns coming from datasources: we don't want to Snapshot those 1545 ColumnNames_t dsColumnsWithoutSizeColumns; 1546 std::copy_if(dsColumns.begin(), dsColumns.end(), std::back_inserter(dsColumnsWithoutSizeColumns), 1547 [](const std::string &name) { return name.size() < 13 || name.substr(0, 13) != "R_rdf_sizeof_"; }); 1548 ColumnNames_t columnNames; 1549 columnNames.reserve(definedColumns.size() + dsColumnsWithoutSizeColumns.size()); 1550 columnNames.insert(columnNames.end(), definedColumns.begin(), definedColumns.end()); 1551 columnNames.insert(columnNames.end(), dsColumnsWithoutSizeColumns.begin(), dsColumnsWithoutSizeColumns.end()); 1552 1553 // The only way we can get duplicate entries is if a column coming from a tree or data-source is Redefine'd. 1554 // RemoveDuplicates should preserve ordering of the columns: it might be meaningful. 1555 RDFInternal::RemoveDuplicates(columnNames); 1556 1557 std::vector<std::string> selectedColumns; 1558 try { 1559 selectedColumns = RDFInternal::ConvertRegexToColumns(columnNames, columnNameRegexp, "Snapshot"); 1560 } 1561 catch (const std::runtime_error &e){ 1562 // No columns were found, try again but consider all input data source columns 1563 if (auto ds = GetDataSource()) 1564 selectedColumns = RDFInternal::ConvertRegexToColumns(ds->GetColumnNames(), columnNameRegexp, "Snapshot"); 1565 else 1566 throw e; 1567 } 1568 1569 if (RDFInternal::GetDataSourceLabel(*this) == "RNTupleDS") { 1570 RDFInternal::RemoveRNTupleSubfields(selectedColumns); 1571 } 1572 1573 return Snapshot(treename, filename, selectedColumns, options); 1574 } 1575 // clang-format on 1576 1577 // clang-format off 1578 //////////////////////////////////////////////////////////////////////////// 1579 /// \brief Save selected columns to disk, in a new TTree or RNTuple `treename` in file `filename`. 1580 /// \param[in] treename The name of the output TTree or RNTuple. 1581 /// \param[in] filename The name of the output TFile. 1582 /// \param[in] columnList The list of names of the columns/branches to be written. 1583 /// \param[in] options RSnapshotOptions struct with extra options to pass to TFile and TTree/RNTuple. 1584 /// \return a `RDataFrame` that wraps the snapshotted dataset. 1585 /// 1586 /// This function returns a `RDataFrame` built with the output TTree or RNTuple as a source. 1587 /// The types of the columns are automatically inferred and do not need to be specified. 1588 /// 1589 /// See Snapshot(std::string_view, std::string_view, const ColumnNames_t&, const RSnapshotOptions &) for a more complete description and example usages. 1590 RResultPtr<RInterface<RLoopManager>> Snapshot(std::string_view treename, std::string_view filename, 1591 std::initializer_list<std::string> columnList, 1592 const RSnapshotOptions &options = RSnapshotOptions()) 1593 { 1594 ColumnNames_t selectedColumns(columnList); 1595 return Snapshot(treename, filename, selectedColumns, options); 1596 } 1597 // clang-format on 1598 1599 //////////////////////////////////////////////////////////////////////////// 1600 /// \brief Save selected columns in memory. 1601 /// \tparam ColumnTypes variadic list of branch/column types. 1602 /// \param[in] columnList columns to be cached in memory. 1603 /// \return a `RDataFrame` that wraps the cached dataset. 1604 /// 1605 /// This action returns a new `RDataFrame` object, completely detached from 1606 /// the originating `RDataFrame`. The new dataframe only contains the cached 1607 /// columns and stores their content in memory for fast, zero-copy subsequent access. 1608 /// 1609 /// Use `Cache` if you know you will only need a subset of the (`Filter`ed) data that 1610 /// fits in memory and that will be accessed many times. 1611 /// 1612 /// \note Cache will refuse to process columns with names of the form `#columnname`. These are special columns 1613 /// made available by some data sources (e.g. RNTupleDS) that represent the size of column `columnname`, and are 1614 /// not meant to be written out with that name (which is not a valid C++ variable name). Instead, go through an 1615 /// Alias(): `df.Alias("nbar", "#bar").Cache<std::size_t>(..., {"nbar"})`. 1616 /// 1617 /// ### Example usage: 1618 /// 1619 /// **Types and columns specified:** 1620 /// ~~~{.cpp} 1621 /// auto cache_some_cols_df = df.Cache<double, MyClass, int>({"col0", "col1", "col2"}); 1622 /// ~~~ 1623 /// 1624 /// **Types inferred and columns specified (this invocation relies on jitting):** 1625 /// ~~~{.cpp} 1626 /// auto cache_some_cols_df = df.Cache({"col0", "col1", "col2"}); 1627 /// ~~~ 1628 /// 1629 /// **Types inferred and columns selected with a regexp (this invocation relies on jitting):** 1630 /// ~~~{.cpp} 1631 /// auto cache_all_cols_df = df.Cache(myRegexp); 1632 /// ~~~ 1633 template <typename... ColumnTypes> 1634 RInterface<RLoopManager> Cache(const ColumnNames_t &columnList) 1635 { 1636 auto staticSeq = std::make_index_sequence<sizeof...(ColumnTypes)>(); 1637 return CacheImpl<ColumnTypes...>(columnList, staticSeq); 1638 } 1639 1640 //////////////////////////////////////////////////////////////////////////// 1641 /// \brief Save selected columns in memory. 1642 /// \param[in] columnList columns to be cached in memory 1643 /// \return a `RDataFrame` that wraps the cached dataset. 1644 /// 1645 /// See the previous overloads for more information. 1646 RInterface<RLoopManager> Cache(const ColumnNames_t &columnList) 1647 { 1648 // Early return: if the list of columns is empty, just return an empty RDF 1649 // If we proceed, the jitted call will not compile! 1650 if (columnList.empty()) { 1651 auto nEntries = *this->Count(); 1652 RInterface<RLoopManager> emptyRDF(std::make_shared<RLoopManager>(nEntries)); 1653 return emptyRDF; 1654 } 1655 1656 std::stringstream cacheCall; 1657 auto upcastNode = RDFInternal::UpcastNode(fProxiedPtr); 1658 RInterface<TTraits::TakeFirstParameter_t<decltype(upcastNode)>> upcastInterface(fProxiedPtr, *fLoopManager, 1659 fColRegister); 1660 // build a string equivalent to 1661 // "(RInterface<nodetype*>*)(this)->Cache<Ts...>(*(ColumnNames_t*)(&columnList))" 1662 RInterface<RLoopManager> resRDF(std::make_shared<ROOT::Detail::RDF::RLoopManager>(0)); 1663 cacheCall << "*reinterpret_cast<ROOT::RDF::RInterface<ROOT::Detail::RDF::RLoopManager>*>(" 1664 << RDFInternal::PrettyPrintAddr(&resRDF) 1665 << ") = reinterpret_cast<ROOT::RDF::RInterface<ROOT::Detail::RDF::RNodeBase>*>(" 1666 << RDFInternal::PrettyPrintAddr(&upcastInterface) << ")->Cache<"; 1667 1668 const auto columnListWithoutSizeColumns = RDFInternal::FilterArraySizeColNames(columnList, "Cache"); 1669 1670 const auto validColumnNames = 1671 GetValidatedColumnNames(columnListWithoutSizeColumns.size(), columnListWithoutSizeColumns); 1672 const auto colTypes = 1673 GetValidatedArgTypes(validColumnNames, fColRegister, nullptr, GetDataSource(), "Cache", /*vector2RVec=*/false); 1674 for (const auto &colType : colTypes) 1675 cacheCall << colType << ", "; 1676 if (!columnListWithoutSizeColumns.empty()) 1677 cacheCall.seekp(-2, cacheCall.cur); // remove the last ", 1678 cacheCall << ">(*reinterpret_cast<std::vector<std::string>*>(" // vector<string> should be ColumnNames_t 1679 << RDFInternal::PrettyPrintAddr(&columnListWithoutSizeColumns) << "));"; 1680 1681 // book the code to jit with the RLoopManager and trigger the event loop 1682 fLoopManager->ToJitExec(cacheCall.str()); 1683 fLoopManager->Jit(); 1684 1685 return resRDF; 1686 } 1687 1688 //////////////////////////////////////////////////////////////////////////// 1689 /// \brief Save selected columns in memory. 1690 /// \param[in] columnNameRegexp The regular expression to match the column names to be selected. The presence of a '^' and a '$' at the end of the string is implicitly assumed if they are not specified. The dialect supported is PCRE via the TPRegexp class. An empty string signals the selection of all columns. 1691 /// \return a `RDataFrame` that wraps the cached dataset. 1692 /// 1693 /// The existing columns are matched against the regular expression. If the string provided 1694 /// is empty, all columns are selected. See the previous overloads for more information. 1695 RInterface<RLoopManager> Cache(std::string_view columnNameRegexp = "") 1696 { 1697 const auto definedColumns = fColRegister.GenerateColumnNames(); 1698 const auto dsColumns = GetDataSource() ? GetDataSource()->GetColumnNames() : ColumnNames_t{}; 1699 // Ignore R_rdf_sizeof_* columns coming from datasources: we don't want to Snapshot those 1700 ColumnNames_t dsColumnsWithoutSizeColumns; 1701 std::copy_if(dsColumns.begin(), dsColumns.end(), std::back_inserter(dsColumnsWithoutSizeColumns), 1702 [](const std::string &name) { return name.size() < 13 || name.substr(0, 13) != "R_rdf_sizeof_"; }); 1703 ColumnNames_t columnNames; 1704 columnNames.reserve(definedColumns.size() + dsColumns.size()); 1705 columnNames.insert(columnNames.end(), definedColumns.begin(), definedColumns.end()); 1706 columnNames.insert(columnNames.end(), dsColumns.begin(), dsColumns.end()); 1707 const auto selectedColumns = RDFInternal::ConvertRegexToColumns(columnNames, columnNameRegexp, "Cache"); 1708 return Cache(selectedColumns); 1709 } 1710 1711 //////////////////////////////////////////////////////////////////////////// 1712 /// \brief Save selected columns in memory. 1713 /// \param[in] columnList columns to be cached in memory. 1714 /// \return a `RDataFrame` that wraps the cached dataset. 1715 /// 1716 /// See the previous overloads for more information. 1717 RInterface<RLoopManager> Cache(std::initializer_list<std::string> columnList) 1718 { 1719 ColumnNames_t selectedColumns(columnList); 1720 return Cache(selectedColumns); 1721 } 1722 1723 // clang-format off 1724 //////////////////////////////////////////////////////////////////////////// 1725 /// \brief Creates a node that filters entries based on range: [begin, end). 1726 /// \param[in] begin Initial entry number considered for this range. 1727 /// \param[in] end Final entry number (excluded) considered for this range. 0 means that the range goes until the end of the dataset. 1728 /// \param[in] stride Process one entry of the [begin, end) range every `stride` entries. Must be strictly greater than 0. 1729 /// \return the first node of the computation graph for which the event loop is limited to a certain range of entries. 1730 /// 1731 /// Note that in case of previous Ranges and Filters the selected range refers to the transformed dataset. 1732 /// Ranges are only available if EnableImplicitMT has _not_ been called. Multi-thread ranges are not supported. 1733 /// 1734 /// ### Example usage: 1735 /// ~~~{.cpp} 1736 /// auto d_0_30 = d.Range(0, 30); // Pick the first 30 entries 1737 /// auto d_15_end = d.Range(15, 0); // Pick all entries from 15 onwards 1738 /// auto d_15_end_3 = d.Range(15, 0, 3); // Stride: from event 15, pick an event every 3 1739 /// ~~~ 1740 // clang-format on 1741 RInterface<RDFDetail::RRange<Proxied>> Range(unsigned int begin, unsigned int end, unsigned int stride = 1) 1742 { 1743 // check invariants 1744 if (stride == 0 || (end != 0 && end < begin)) 1745 throw std::runtime_error("Range: stride must be strictly greater than 0 and end must be greater than begin."); 1746 CheckIMTDisabled("Range"); 1747 1748 using Range_t = RDFDetail::RRange<Proxied>; 1749 auto rangePtr = std::make_shared<Range_t>(begin, end, stride, fProxiedPtr); 1750 RInterface<RDFDetail::RRange<Proxied>> newInterface(std::move(rangePtr), *fLoopManager, fColRegister); 1751 return newInterface; 1752 } 1753 1754 // clang-format off 1755 //////////////////////////////////////////////////////////////////////////// 1756 /// \brief Creates a node that filters entries based on range. 1757 /// \param[in] end Final entry number (excluded) considered for this range. 0 means that the range goes until the end of the dataset. 1758 /// \return a node of the computation graph for which the range is defined. 1759 /// 1760 /// See the other Range overload for a detailed description. 1761 // clang-format on 1762 RInterface<RDFDetail::RRange<Proxied>> Range(unsigned int end) { return Range(0, end, 1); } 1763 1764 // clang-format off 1765 //////////////////////////////////////////////////////////////////////////// 1766 /// \brief Execute a user-defined function on each entry (*instant action*). 1767 /// \param[in] f Function, lambda expression, functor class or any other callable object performing user defined calculations. 1768 /// \param[in] columns Names of the columns/branches in input to the user function. 1769 /// 1770 /// The callable `f` is invoked once per entry. This is an *instant action*: 1771 /// upon invocation, an event loop as well as execution of all scheduled actions 1772 /// is triggered. 1773 /// Users are responsible for the thread-safety of this callable when executing 1774 /// with implicit multi-threading enabled (i.e. ROOT::EnableImplicitMT). 1775 /// 1776 /// ### Example usage: 1777 /// ~~~{.cpp} 1778 /// myDf.Foreach([](int i){ std::cout << i << std::endl;}, {"myIntColumn"}); 1779 /// ~~~ 1780 // clang-format on 1781 template <typename F> 1782 void Foreach(F f, const ColumnNames_t &columns = {}) 1783 { 1784 using arg_types = typename TTraits::CallableTraits<decltype(f)>::arg_types_nodecay; 1785 using ret_type = typename TTraits::CallableTraits<decltype(f)>::ret_type; 1786 ForeachSlot(RDFInternal::AddSlotParameter<ret_type>(f, arg_types()), columns); 1787 } 1788 1789 // clang-format off 1790 //////////////////////////////////////////////////////////////////////////// 1791 /// \brief Execute a user-defined function requiring a processing slot index on each entry (*instant action*). 1792 /// \param[in] f Function, lambda expression, functor class or any other callable object performing user defined calculations. 1793 /// \param[in] columns Names of the columns/branches in input to the user function. 1794 /// 1795 /// Same as `Foreach`, but the user-defined function takes an extra 1796 /// `unsigned int` as its first parameter, the *processing slot index*. 1797 /// This *slot index* will be assigned a different value, `0` to `poolSize - 1`, 1798 /// for each thread of execution. 1799 /// This is meant as a helper in writing thread-safe `Foreach` 1800 /// actions when using `RDataFrame` after `ROOT::EnableImplicitMT()`. 1801 /// The user-defined processing callable is able to follow different 1802 /// *streams of processing* indexed by the first parameter. 1803 /// `ForeachSlot` works just as well with single-thread execution: in that 1804 /// case `slot` will always be `0`. 1805 /// 1806 /// ### Example usage: 1807 /// ~~~{.cpp} 1808 /// myDf.ForeachSlot([](unsigned int s, int i){ std::cout << "Slot " << s << ": "<< i << std::endl;}, {"myIntColumn"}); 1809 /// ~~~ 1810 // clang-format on 1811 template <typename F> 1812 void ForeachSlot(F f, const ColumnNames_t &columns = {}) 1813 { 1814 using ColTypes_t = TypeTraits::RemoveFirstParameter_t<typename TTraits::CallableTraits<F>::arg_types>; 1815 constexpr auto nColumns = ColTypes_t::list_size; 1816 1817 const auto validColumnNames = GetValidatedColumnNames(nColumns, columns); 1818 CheckAndFillDSColumns(validColumnNames, ColTypes_t()); 1819 1820 using Helper_t = RDFInternal::ForeachSlotHelper<F>; 1821 using Action_t = RDFInternal::RAction<Helper_t, Proxied>; 1822 1823 auto action = std::make_unique<Action_t>(Helper_t(std::move(f)), validColumnNames, fProxiedPtr, fColRegister); 1824 1825 fLoopManager->Run(); 1826 } 1827 1828 // clang-format off 1829 //////////////////////////////////////////////////////////////////////////// 1830 /// \brief Execute a user-defined reduce operation on the values of a column. 1831 /// \tparam F The type of the reduce callable. Automatically deduced. 1832 /// \tparam T The type of the column to apply the reduction to. Automatically deduced. 1833 /// \param[in] f A callable with signature `T(T,T)` 1834 /// \param[in] columnName The column to be reduced. If omitted, the first default column is used instead. 1835 /// \return the reduced quantity wrapped in a ROOT::RDF:RResultPtr. 1836 /// 1837 /// A reduction takes two values of a column and merges them into one (e.g. 1838 /// by summing them, taking the maximum, etc). This action performs the 1839 /// specified reduction operation on all processed column values, returning 1840 /// a single value of the same type. The callable f must satisfy the general 1841 /// requirements of a *processing function* besides having signature `T(T,T)` 1842 /// where `T` is the type of column columnName. 1843 /// 1844 /// The returned reduced value of each thread (e.g. the initial value of a sum) is initialized to a 1845 /// default-constructed T object. This is commonly expected to be the neutral/identity element for the specific 1846 /// reduction operation `f` (e.g. 0 for a sum, 1 for a product). If a default-constructed T does not satisfy this 1847 /// requirement, users should explicitly specify an initialization value for T by calling the appropriate `Reduce` 1848 /// overload. 1849 /// 1850 /// ### Example usage: 1851 /// ~~~{.cpp} 1852 /// auto sumOfIntCol = d.Reduce([](int x, int y) { return x + y; }, "intCol"); 1853 /// ~~~ 1854 /// 1855 /// This action is *lazy*: upon invocation of this method the calculation is 1856 /// booked but not executed. Also see RResultPtr. 1857 // clang-format on 1858 template <typename F, typename T = typename TTraits::CallableTraits<F>::ret_type> 1859 RResultPtr<T> Reduce(F f, std::string_view columnName = "") 1860 { 1861 static_assert( 1862 std::is_default_constructible<T>::value, 1863 "reduce object cannot be default-constructed. Please provide an initialisation value (redIdentity)"); 1864 return Reduce(std::move(f), columnName, T()); 1865 } 1866 1867 //////////////////////////////////////////////////////////////////////////// 1868 /// \brief Execute a user-defined reduce operation on the values of a column. 1869 /// \tparam F The type of the reduce callable. Automatically deduced. 1870 /// \tparam T The type of the column to apply the reduction to. Automatically deduced. 1871 /// \param[in] f A callable with signature `T(T,T)` 1872 /// \param[in] columnName The column to be reduced. If omitted, the first default column is used instead. 1873 /// \param[in] redIdentity The reduced object of each thread is initialized to this value. 1874 /// \return the reduced quantity wrapped in a RResultPtr. 1875 /// 1876 /// ### Example usage: 1877 /// ~~~{.cpp} 1878 /// auto sumOfIntColWithOffset = d.Reduce([](int x, int y) { return x + y; }, "intCol", 42); 1879 /// ~~~ 1880 /// See the description of the first Reduce overload for more information. 1881 template <typename F, typename T = typename TTraits::CallableTraits<F>::ret_type> 1882 RResultPtr<T> Reduce(F f, std::string_view columnName, const T &redIdentity) 1883 { 1884 return Aggregate(f, f, columnName, redIdentity); 1885 } 1886 1887 //////////////////////////////////////////////////////////////////////////// 1888 /// \brief Return the number of entries processed (*lazy action*). 1889 /// \return the number of entries wrapped in a RResultPtr. 1890 /// 1891 /// Useful e.g. for counting the number of entries passing a certain filter (see also `Report`). 1892 /// This action is *lazy*: upon invocation of this method the calculation is 1893 /// booked but not executed. Also see RResultPtr. 1894 /// 1895 /// ### Example usage: 1896 /// ~~~{.cpp} 1897 /// auto nEntriesAfterCuts = myFilteredDf.Count(); 1898 /// ~~~ 1899 /// 1900 RResultPtr<ULong64_t> Count() 1901 { 1902 const auto nSlots = fLoopManager->GetNSlots(); 1903 auto cSPtr = std::make_shared<ULong64_t>(0); 1904 using Helper_t = RDFInternal::CountHelper; 1905 using Action_t = RDFInternal::RAction<Helper_t, Proxied>; 1906 auto action = std::make_unique<Action_t>(Helper_t(cSPtr, nSlots), ColumnNames_t({}), fProxiedPtr, 1907 RDFInternal::RColumnRegister(fColRegister)); 1908 return MakeResultPtr(cSPtr, *fLoopManager, std::move(action)); 1909 } 1910 1911 //////////////////////////////////////////////////////////////////////////// 1912 /// \brief Return a collection of values of a column (*lazy action*, returns a std::vector by default). 1913 /// \tparam T The type of the column. 1914 /// \tparam COLL The type of collection used to store the values. 1915 /// \param[in] column The name of the column to collect the values of. 1916 /// \return the content of the selected column wrapped in a RResultPtr. 1917 /// 1918 /// The collection type to be specified for C-style array columns is `RVec<T>`: 1919 /// in this case the returned collection is a `std::vector<RVec<T>>`. 1920 /// ### Example usage: 1921 /// ~~~{.cpp} 1922 /// // In this case intCol is a std::vector<int> 1923 /// auto intCol = rdf.Take<int>("integerColumn"); 1924 /// // Same content as above but in this case taken as a RVec<int> 1925 /// auto intColAsRVec = rdf.Take<int, RVec<int>>("integerColumn"); 1926 /// // In this case intCol is a std::vector<RVec<int>>, a collection of collections 1927 /// auto cArrayIntCol = rdf.Take<RVec<int>>("cArrayInt"); 1928 /// ~~~ 1929 /// This action is *lazy*: upon invocation of this method the calculation is 1930 /// booked but not executed. Also see RResultPtr. 1931 template <typename T, typename COLL = std::vector<T>> 1932 RResultPtr<COLL> Take(std::string_view column = "") 1933 { 1934 const auto columns = column.empty() ? ColumnNames_t() : ColumnNames_t({std::string(column)}); 1935 1936 const auto validColumnNames = GetValidatedColumnNames(1, columns); 1937 CheckAndFillDSColumns(validColumnNames, TTraits::TypeList<T>()); 1938 1939 using Helper_t = RDFInternal::TakeHelper<T, T, COLL>; 1940 using Action_t = RDFInternal::RAction<Helper_t, Proxied>; 1941 auto valuesPtr = std::make_shared<COLL>(); 1942 const auto nSlots = fLoopManager->GetNSlots(); 1943 1944 auto action = 1945 std::make_unique<Action_t>(Helper_t(valuesPtr, nSlots), validColumnNames, fProxiedPtr, fColRegister); 1946 return MakeResultPtr(valuesPtr, *fLoopManager, std::move(action)); 1947 } 1948 1949 //////////////////////////////////////////////////////////////////////////// 1950 /// \brief Fill and return a one-dimensional histogram with the values of a column (*lazy action*). 1951 /// \tparam V The type of the column used to fill the histogram. 1952 /// \param[in] model The returned histogram will be constructed using this as a model. 1953 /// \param[in] vName The name of the column that will fill the histogram. 1954 /// \return the monodimensional histogram wrapped in a RResultPtr. 1955 /// 1956 /// Columns can be of a container type (e.g. `std::vector<double>`), in which case the histogram 1957 /// is filled with each one of the elements of the container. In case multiple columns of container type 1958 /// are provided (e.g. values and weights) they must have the same length for each one of the events (but 1959 /// possibly different lengths between events). 1960 /// This action is *lazy*: upon invocation of this method the calculation is 1961 /// booked but not executed. Also see RResultPtr. 1962 /// 1963 /// ### Example usage: 1964 /// ~~~{.cpp} 1965 /// // Deduce column type (this invocation needs jitting internally) 1966 /// auto myHist1 = myDf.Histo1D({"histName", "histTitle", 64u, 0., 128.}, "myColumn"); 1967 /// // Explicit column type 1968 /// auto myHist2 = myDf.Histo1D<float>({"histName", "histTitle", 64u, 0., 128.}, "myColumn"); 1969 /// ~~~ 1970 /// 1971 /// \note Differently from other ROOT interfaces, the returned histogram is not associated to gDirectory 1972 /// and the caller is responsible for its lifetime (in particular, a typical source of confusion is that 1973 /// if result histograms go out of scope before the end of the program, ROOT might display a blank canvas). 1974 template <typename V = RDFDetail::RInferredType> 1975 RResultPtr<::TH1D> Histo1D(const TH1DModel &model = {"", "", 128u, 0., 0.}, std::string_view vName = "") 1976 { 1977 const auto userColumns = vName.empty() ? ColumnNames_t() : ColumnNames_t({std::string(vName)}); 1978 1979 const auto validatedColumns = GetValidatedColumnNames(1, userColumns); 1980 1981 std::shared_ptr<::TH1D> h(nullptr); 1982 { 1983 ROOT::Internal::RDF::RIgnoreErrorLevelRAII iel(kError); 1984 h = model.GetHistogram(); 1985 } 1986 1987 if (h->GetXaxis()->GetXmax() == h->GetXaxis()->GetXmin()) 1988 h->SetCanExtend(::TH1::kAllAxes); 1989 return CreateAction<RDFInternal::ActionTags::Histo1D, V>(validatedColumns, h, h, fProxiedPtr); 1990 } 1991 1992 //////////////////////////////////////////////////////////////////////////// 1993 /// \brief Fill and return a one-dimensional histogram with the values of a column (*lazy action*). 1994 /// \tparam V The type of the column used to fill the histogram. 1995 /// \param[in] vName The name of the column that will fill the histogram. 1996 /// \return the monodimensional histogram wrapped in a RResultPtr. 1997 /// 1998 /// This overload uses a default model histogram TH1D(name, title, 128u, 0., 0.). 1999 /// The "name" and "title" strings are built starting from the input column name. 2000 /// See the description of the first Histo1D() overload for more details. 2001 /// 2002 /// ### Example usage: 2003 /// ~~~{.cpp} 2004 /// // Deduce column type (this invocation needs jitting internally) 2005 /// auto myHist1 = myDf.Histo1D("myColumn"); 2006 /// // Explicit column type 2007 /// auto myHist2 = myDf.Histo1D<float>("myColumn"); 2008 /// ~~~ 2009 template <typename V = RDFDetail::RInferredType> 2010 RResultPtr<::TH1D> Histo1D(std::string_view vName) 2011 { 2012 const auto h_name = std::string(vName); 2013 const auto h_title = h_name + ";" + h_name + ";count"; 2014 return Histo1D<V>({h_name.c_str(), h_title.c_str(), 128u, 0., 0.}, vName); 2015 } 2016 2017 //////////////////////////////////////////////////////////////////////////// 2018 /// \brief Fill and return a one-dimensional histogram with the weighted values of a column (*lazy action*). 2019 /// \tparam V The type of the column used to fill the histogram. 2020 /// \tparam W The type of the column used as weights. 2021 /// \param[in] model The returned histogram will be constructed using this as a model. 2022 /// \param[in] vName The name of the column that will fill the histogram. 2023 /// \param[in] wName The name of the column that will provide the weights. 2024 /// \return the monodimensional histogram wrapped in a RResultPtr. 2025 /// 2026 /// See the description of the first Histo1D() overload for more details. 2027 /// 2028 /// ### Example usage: 2029 /// ~~~{.cpp} 2030 /// // Deduce column type (this invocation needs jitting internally) 2031 /// auto myHist1 = myDf.Histo1D({"histName", "histTitle", 64u, 0., 128.}, "myValue", "myweight"); 2032 /// // Explicit column type 2033 /// auto myHist2 = myDf.Histo1D<float, int>({"histName", "histTitle", 64u, 0., 128.}, "myValue", "myweight"); 2034 /// ~~~ 2035 template <typename V = RDFDetail::RInferredType, typename W = RDFDetail::RInferredType> 2036 RResultPtr<::TH1D> Histo1D(const TH1DModel &model, std::string_view vName, std::string_view wName) 2037 { 2038 const std::vector<std::string_view> columnViews = {vName, wName}; 2039 const auto userColumns = RDFInternal::AtLeastOneEmptyString(columnViews) 2040 ? ColumnNames_t() 2041 : ColumnNames_t(columnViews.begin(), columnViews.end()); 2042 std::shared_ptr<::TH1D> h(nullptr); 2043 { 2044 ROOT::Internal::RDF::RIgnoreErrorLevelRAII iel(kError); 2045 h = model.GetHistogram(); 2046 } 2047 2048 if (h->GetXaxis()->GetXmax() == h->GetXaxis()->GetXmin()) 2049 h->SetCanExtend(::TH1::kAllAxes); 2050 return CreateAction<RDFInternal::ActionTags::Histo1D, V, W>(userColumns, h, h, fProxiedPtr); 2051 } 2052 2053 //////////////////////////////////////////////////////////////////////////// 2054 /// \brief Fill and return a one-dimensional histogram with the weighted values of a column (*lazy action*). 2055 /// \tparam V The type of the column used to fill the histogram. 2056 /// \tparam W The type of the column used as weights. 2057 /// \param[in] vName The name of the column that will fill the histogram. 2058 /// \param[in] wName The name of the column that will provide the weights. 2059 /// \return the monodimensional histogram wrapped in a RResultPtr. 2060 /// 2061 /// This overload uses a default model histogram TH1D(name, title, 128u, 0., 0.). 2062 /// The "name" and "title" strings are built starting from the input column names. 2063 /// See the description of the first Histo1D() overload for more details. 2064 /// 2065 /// ### Example usage: 2066 /// ~~~{.cpp} 2067 /// // Deduce column types (this invocation needs jitting internally) 2068 /// auto myHist1 = myDf.Histo1D("myValue", "myweight"); 2069 /// // Explicit column types 2070 /// auto myHist2 = myDf.Histo1D<float, int>("myValue", "myweight"); 2071 /// ~~~ 2072 template <typename V = RDFDetail::RInferredType, typename W = RDFDetail::RInferredType> 2073 RResultPtr<::TH1D> Histo1D(std::string_view vName, std::string_view wName) 2074 { 2075 // We build name and title based on the value and weight column names 2076 std::string str_vName{vName}; 2077 std::string str_wName{wName}; 2078 const auto h_name = str_vName + "_weighted_" + str_wName; 2079 const auto h_title = str_vName + ", weights: " + str_wName + ";" + str_vName + ";count * " + str_wName; 2080 return Histo1D<V, W>({h_name.c_str(), h_title.c_str(), 128u, 0., 0.}, vName, wName); 2081 } 2082 2083 //////////////////////////////////////////////////////////////////////////// 2084 /// \brief Fill and return a one-dimensional histogram with the weighted values of a column (*lazy action*). 2085 /// \tparam V The type of the column used to fill the histogram. 2086 /// \tparam W The type of the column used as weights. 2087 /// \param[in] model The returned histogram will be constructed using this as a model. 2088 /// \return the monodimensional histogram wrapped in a RResultPtr. 2089 /// 2090 /// This overload will use the first two default columns as column names. 2091 /// See the description of the first Histo1D() overload for more details. 2092 template <typename V, typename W> 2093 RResultPtr<::TH1D> Histo1D(const TH1DModel &model = {"", "", 128u, 0., 0.}) 2094 { 2095 return Histo1D<V, W>(model, "", ""); 2096 } 2097 2098 //////////////////////////////////////////////////////////////////////////// 2099 /// \brief Fill and return a two-dimensional histogram (*lazy action*). 2100 /// \tparam V1 The type of the column used to fill the x axis of the histogram. 2101 /// \tparam V2 The type of the column used to fill the y axis of the histogram. 2102 /// \param[in] model The returned histogram will be constructed using this as a model. 2103 /// \param[in] v1Name The name of the column that will fill the x axis. 2104 /// \param[in] v2Name The name of the column that will fill the y axis. 2105 /// \return the bidimensional histogram wrapped in a RResultPtr. 2106 /// 2107 /// Columns can be of a container type (e.g. std::vector<double>), in which case the histogram 2108 /// is filled with each one of the elements of the container. In case multiple columns of container type 2109 /// are provided (e.g. values and weights) they must have the same length for each one of the events (but 2110 /// possibly different lengths between events). 2111 /// This action is *lazy*: upon invocation of this method the calculation is 2112 /// booked but not executed. Also see RResultPtr. 2113 /// 2114 /// ### Example usage: 2115 /// ~~~{.cpp} 2116 /// // Deduce column types (this invocation needs jitting internally) 2117 /// auto myHist1 = myDf.Histo2D({"histName", "histTitle", 64u, 0., 128., 32u, -4., 4.}, "myValueX", "myValueY"); 2118 /// // Explicit column types 2119 /// auto myHist2 = myDf.Histo2D<float, float>({"histName", "histTitle", 64u, 0., 128., 32u, -4., 4.}, "myValueX", "myValueY"); 2120 /// ~~~ 2121 /// 2122 /// 2123 /// \note Differently from other ROOT interfaces, the returned histogram is not associated to gDirectory 2124 /// and the caller is responsible for its lifetime (in particular, a typical source of confusion is that 2125 /// if result histograms go out of scope before the end of the program, ROOT might display a blank canvas). 2126 template <typename V1 = RDFDetail::RInferredType, typename V2 = RDFDetail::RInferredType> 2127 RResultPtr<::TH2D> Histo2D(const TH2DModel &model, std::string_view v1Name = "", std::string_view v2Name = "") 2128 { 2129 std::shared_ptr<::TH2D> h(nullptr); 2130 { 2131 ROOT::Internal::RDF::RIgnoreErrorLevelRAII iel(kError); 2132 h = model.GetHistogram(); 2133 } 2134 if (!RDFInternal::HistoUtils<::TH2D>::HasAxisLimits(*h)) { 2135 throw std::runtime_error("2D histograms with no axes limits are not supported yet."); 2136 } 2137 const std::vector<std::string_view> columnViews = {v1Name, v2Name}; 2138 const auto userColumns = RDFInternal::AtLeastOneEmptyString(columnViews) 2139 ? ColumnNames_t() 2140 : ColumnNames_t(columnViews.begin(), columnViews.end()); 2141 return CreateAction<RDFInternal::ActionTags::Histo2D, V1, V2>(userColumns, h, h, fProxiedPtr); 2142 } 2143 2144 //////////////////////////////////////////////////////////////////////////// 2145 /// \brief Fill and return a weighted two-dimensional histogram (*lazy action*). 2146 /// \tparam V1 The type of the column used to fill the x axis of the histogram. 2147 /// \tparam V2 The type of the column used to fill the y axis of the histogram. 2148 /// \tparam W The type of the column used for the weights of the histogram. 2149 /// \param[in] model The returned histogram will be constructed using this as a model. 2150 /// \param[in] v1Name The name of the column that will fill the x axis. 2151 /// \param[in] v2Name The name of the column that will fill the y axis. 2152 /// \param[in] wName The name of the column that will provide the weights. 2153 /// \return the bidimensional histogram wrapped in a RResultPtr. 2154 /// 2155 /// This action is *lazy*: upon invocation of this method the calculation is 2156 /// booked but not executed. Also see RResultPtr. 2157 /// 2158 /// ### Example usage: 2159 /// ~~~{.cpp} 2160 /// // Deduce column types (this invocation needs jitting internally) 2161 /// auto myHist1 = myDf.Histo2D({"histName", "histTitle", 64u, 0., 128., 32u, -4., 4.}, "myValueX", "myValueY", "myWeight"); 2162 /// // Explicit column types 2163 /// auto myHist2 = myDf.Histo2D<float, float, double>({"histName", "histTitle", 64u, 0., 128., 32u, -4., 4.}, "myValueX", "myValueY", "myWeight"); 2164 /// ~~~ 2165 /// 2166 /// See the documentation of the first Histo2D() overload for more details. 2167 template <typename V1 = RDFDetail::RInferredType, typename V2 = RDFDetail::RInferredType, 2168 typename W = RDFDetail::RInferredType> 2169 RResultPtr<::TH2D> 2170 Histo2D(const TH2DModel &model, std::string_view v1Name, std::string_view v2Name, std::string_view wName) 2171 { 2172 std::shared_ptr<::TH2D> h(nullptr); 2173 { 2174 ROOT::Internal::RDF::RIgnoreErrorLevelRAII iel(kError); 2175 h = model.GetHistogram(); 2176 } 2177 if (!RDFInternal::HistoUtils<::TH2D>::HasAxisLimits(*h)) { 2178 throw std::runtime_error("2D histograms with no axes limits are not supported yet."); 2179 } 2180 const std::vector<std::string_view> columnViews = {v1Name, v2Name, wName}; 2181 const auto userColumns = RDFInternal::AtLeastOneEmptyString(columnViews) 2182 ? ColumnNames_t() 2183 : ColumnNames_t(columnViews.begin(), columnViews.end()); 2184 return CreateAction<RDFInternal::ActionTags::Histo2D, V1, V2, W>(userColumns, h, h, fProxiedPtr); 2185 } 2186 2187 template <typename V1, typename V2, typename W> 2188 RResultPtr<::TH2D> Histo2D(const TH2DModel &model) 2189 { 2190 return Histo2D<V1, V2, W>(model, "", "", ""); 2191 } 2192 2193 //////////////////////////////////////////////////////////////////////////// 2194 /// \brief Fill and return a three-dimensional histogram (*lazy action*). 2195 /// \tparam V1 The type of the column used to fill the x axis of the histogram. Inferred if not present. 2196 /// \tparam V2 The type of the column used to fill the y axis of the histogram. Inferred if not present. 2197 /// \tparam V3 The type of the column used to fill the z axis of the histogram. Inferred if not present. 2198 /// \param[in] model The returned histogram will be constructed using this as a model. 2199 /// \param[in] v1Name The name of the column that will fill the x axis. 2200 /// \param[in] v2Name The name of the column that will fill the y axis. 2201 /// \param[in] v3Name The name of the column that will fill the z axis. 2202 /// \return the tridimensional histogram wrapped in a RResultPtr. 2203 /// 2204 /// This action is *lazy*: upon invocation of this method the calculation is 2205 /// booked but not executed. Also see RResultPtr. 2206 /// 2207 /// ### Example usage: 2208 /// ~~~{.cpp} 2209 /// // Deduce column types (this invocation needs jitting internally) 2210 /// auto myHist1 = myDf.Histo3D({"name", "title", 64u, 0., 128., 32u, -4., 4., 8u, -2., 2.}, 2211 /// "myValueX", "myValueY", "myValueZ"); 2212 /// // Explicit column types 2213 /// auto myHist2 = myDf.Histo3D<double, double, float>({"name", "title", 64u, 0., 128., 32u, -4., 4., 8u, -2., 2.}, 2214 /// "myValueX", "myValueY", "myValueZ"); 2215 /// ~~~ 2216 /// \note If three-dimensional histograms consume too much memory in multithreaded runs, the cloning of TH3D 2217 /// per thread can be reduced using ROOT::RDF::Experimental::ThreadsPerTH3(). See the section "Memory Usage" in 2218 /// the RDataFrame description. 2219 /// \note Differently from other ROOT interfaces, the returned histogram is not associated to gDirectory 2220 /// and the caller is responsible for its lifetime (in particular, a typical source of confusion is that 2221 /// if result histograms go out of scope before the end of the program, ROOT might display a blank canvas). 2222 template <typename V1 = RDFDetail::RInferredType, typename V2 = RDFDetail::RInferredType, 2223 typename V3 = RDFDetail::RInferredType> 2224 RResultPtr<::TH3D> Histo3D(const TH3DModel &model, std::string_view v1Name = "", std::string_view v2Name = "", 2225 std::string_view v3Name = "") 2226 { 2227 std::shared_ptr<::TH3D> h(nullptr); 2228 { 2229 ROOT::Internal::RDF::RIgnoreErrorLevelRAII iel(kError); 2230 h = model.GetHistogram(); 2231 } 2232 if (!RDFInternal::HistoUtils<::TH3D>::HasAxisLimits(*h)) { 2233 throw std::runtime_error("3D histograms with no axes limits are not supported yet."); 2234 } 2235 const std::vector<std::string_view> columnViews = {v1Name, v2Name, v3Name}; 2236 const auto userColumns = RDFInternal::AtLeastOneEmptyString(columnViews) 2237 ? ColumnNames_t() 2238 : ColumnNames_t(columnViews.begin(), columnViews.end()); 2239 return CreateAction<RDFInternal::ActionTags::Histo3D, V1, V2, V3>(userColumns, h, h, fProxiedPtr); 2240 } 2241 2242 //////////////////////////////////////////////////////////////////////////// 2243 /// \brief Fill and return a three-dimensional histogram (*lazy action*). 2244 /// \tparam V1 The type of the column used to fill the x axis of the histogram. Inferred if not present. 2245 /// \tparam V2 The type of the column used to fill the y axis of the histogram. Inferred if not present. 2246 /// \tparam V3 The type of the column used to fill the z axis of the histogram. Inferred if not present. 2247 /// \tparam W The type of the column used for the weights of the histogram. Inferred if not present. 2248 /// \param[in] model The returned histogram will be constructed using this as a model. 2249 /// \param[in] v1Name The name of the column that will fill the x axis. 2250 /// \param[in] v2Name The name of the column that will fill the y axis. 2251 /// \param[in] v3Name The name of the column that will fill the z axis. 2252 /// \param[in] wName The name of the column that will provide the weights. 2253 /// \return the tridimensional histogram wrapped in a RResultPtr. 2254 /// 2255 /// This action is *lazy*: upon invocation of this method the calculation is 2256 /// booked but not executed. Also see RResultPtr. 2257 /// 2258 /// ### Example usage: 2259 /// ~~~{.cpp} 2260 /// // Deduce column types (this invocation needs jitting internally) 2261 /// auto myHist1 = myDf.Histo3D({"name", "title", 64u, 0., 128., 32u, -4., 4., 8u, -2., 2.}, 2262 /// "myValueX", "myValueY", "myValueZ", "myWeight"); 2263 /// // Explicit column types 2264 /// using d_t = double; 2265 /// auto myHist2 = myDf.Histo3D<d_t, d_t, float, d_t>({"name", "title", 64u, 0., 128., 32u, -4., 4., 8u, -2., 2.}, 2266 /// "myValueX", "myValueY", "myValueZ", "myWeight"); 2267 /// ~~~ 2268 /// 2269 /// 2270 /// See the documentation of the first Histo2D() overload for more details. 2271 template <typename V1 = RDFDetail::RInferredType, typename V2 = RDFDetail::RInferredType, 2272 typename V3 = RDFDetail::RInferredType, typename W = RDFDetail::RInferredType> 2273 RResultPtr<::TH3D> Histo3D(const TH3DModel &model, std::string_view v1Name, std::string_view v2Name, 2274 std::string_view v3Name, std::string_view wName) 2275 { 2276 std::shared_ptr<::TH3D> h(nullptr); 2277 { 2278 ROOT::Internal::RDF::RIgnoreErrorLevelRAII iel(kError); 2279 h = model.GetHistogram(); 2280 } 2281 if (!RDFInternal::HistoUtils<::TH3D>::HasAxisLimits(*h)) { 2282 throw std::runtime_error("3D histograms with no axes limits are not supported yet."); 2283 } 2284 const std::vector<std::string_view> columnViews = {v1Name, v2Name, v3Name, wName}; 2285 const auto userColumns = RDFInternal::AtLeastOneEmptyString(columnViews) 2286 ? ColumnNames_t() 2287 : ColumnNames_t(columnViews.begin(), columnViews.end()); 2288 return CreateAction<RDFInternal::ActionTags::Histo3D, V1, V2, V3, W>(userColumns, h, h, fProxiedPtr); 2289 } 2290 2291 template <typename V1, typename V2, typename V3, typename W> 2292 RResultPtr<::TH3D> Histo3D(const TH3DModel &model) 2293 { 2294 return Histo3D<V1, V2, V3, W>(model, "", "", "", ""); 2295 } 2296 2297 //////////////////////////////////////////////////////////////////////////// 2298 /// \brief Fill and return an N-dimensional histogram (*lazy action*). 2299 /// \tparam FirstColumn The first type of the column the values of which are used to fill the object. Inferred if not 2300 /// present. 2301 /// \tparam OtherColumns A list of the other types of the columns the values of which are used to fill the 2302 /// object. 2303 /// \param[in] model The returned histogram will be constructed using this as a model. 2304 /// \param[in] columnList 2305 /// A list containing the names of the columns that will be passed when calling `Fill`. 2306 /// \param[in] wName The name of the column that will provide the weights. 2307 /// \return the N-dimensional histogram wrapped in a RResultPtr. 2308 /// 2309 /// This action is *lazy*: upon invocation of this method the calculation is 2310 /// booked but not executed. See RResultPtr documentation. 2311 /// 2312 /// ### Example usage: 2313 /// ~~~{.cpp} 2314 /// auto myFilledObj = myDf.HistoND<float, float, float, float>({"name","title", 4, 2315 /// {40,40,40,40}, {20.,20.,20.,20.}, {60.,60.,60.,60.}}, 2316 /// {"col0", "col1", "col2", "col3"}); 2317 /// ~~~ 2318 /// 2319 /// \note A column with event weights should not be passed as part of `columnList`, but instead be passed in the new 2320 /// argument `wName`: `HistoND(model, cols, weightCol)`. 2321 /// 2322 template <typename FirstColumn, typename... OtherColumns> // need FirstColumn to disambiguate overloads 2323 RResultPtr<::THnD> HistoND(const THnDModel &model, const ColumnNames_t &columnList, std::string_view wName = "") 2324 { 2325 std::shared_ptr<::THnD> h(nullptr); 2326 { 2327 ROOT::Internal::RDF::RIgnoreErrorLevelRAII iel(kError); 2328 h = model.GetHistogram(); 2329 const auto hDims = h->GetNdimensions(); 2330 decltype(hDims) nCols = columnList.size(); 2331 2332 if (!wName.empty() && nCols == hDims + 1) 2333 throw std::invalid_argument("The weight column was passed as an argument and at the same time the list of " 2334 "input columns contains one column more than the number of dimensions of the " 2335 "histogram. Call as 'HistoND(model, cols, weightCol)'."); 2336 2337 if (nCols == hDims + 1) 2338 Warning("HistoND", "Passing the column with the weights as the last column in the list is deprecated. " 2339 "Instead, pass it as a separate argument, e.g. 'HistoND(model, cols, weightCol)'."); 2340 2341 if (!wName.empty() || nCols == hDims + 1) 2342 h->Sumw2(); 2343 2344 if (nCols != hDims + 1 && nCols != hDims) 2345 throw std::invalid_argument("Wrong number of columns for the specified number of histogram axes."); 2346 } 2347 2348 if (!wName.empty()) { 2349 // The action helper will invoke THnBase::Fill overload that performs weighted filling in case the number of 2350 // passed arguments is one more the number of dimensions of the histogram. 2351 ColumnNames_t userColumns = columnList; 2352 userColumns.push_back(std::string{wName}); 2353 return CreateAction<RDFInternal::ActionTags::HistoND, FirstColumn, OtherColumns...>(userColumns, h, h, 2354 fProxiedPtr); 2355 } 2356 return CreateAction<RDFInternal::ActionTags::HistoND, FirstColumn, OtherColumns...>(columnList, h, h, 2357 fProxiedPtr); 2358 } 2359 2360 //////////////////////////////////////////////////////////////////////////// 2361 /// \brief Fill and return an N-dimensional histogram (*lazy action*). 2362 /// \param[in] model The returned histogram will be constructed using this as a model. 2363 /// \param[in] columnList A list containing the names of the columns that will be passed when calling `Fill` 2364 /// \param[in] wName The name of the column that will provide the weights. 2365 /// \return the N-dimensional histogram wrapped in a RResultPtr. 2366 /// 2367 /// This action is *lazy*: upon invocation of this method the calculation is 2368 /// booked but not executed. Also see RResultPtr. 2369 /// 2370 /// ### Example usage: 2371 /// ~~~{.cpp} 2372 /// auto myFilledObj = myDf.HistoND({"name","title", 4, 2373 /// {40,40,40,40}, {20.,20.,20.,20.}, {60.,60.,60.,60.}}, 2374 /// {"col0", "col1", "col2", "col3"}); 2375 /// ~~~ 2376 /// 2377 /// \note A column with event weights should not be passed as part of `columnList`, but instead be passed in the new 2378 /// argument `wName`: `HistoND(model, cols, weightCol)`. 2379 /// 2380 RResultPtr<::THnD> HistoND(const THnDModel &model, const ColumnNames_t &columnList, std::string_view wName = "") 2381 { 2382 std::shared_ptr<::THnD> h(nullptr); 2383 { 2384 ROOT::Internal::RDF::RIgnoreErrorLevelRAII iel(kError); 2385 h = model.GetHistogram(); 2386 const auto hDims = h->GetNdimensions(); 2387 decltype(hDims) nCols = columnList.size(); 2388 2389 if (!wName.empty() && nCols == hDims + 1) 2390 throw std::invalid_argument("The weight column was passed as an argument and at the same time the list of " 2391 "input columns contains one column more than the number of dimensions of the " 2392 "histogram. Call as 'HistoND(model, cols, weightCol)'."); 2393 2394 if (nCols == hDims + 1) 2395 Warning("HistoND", "Passing the column with the weights as the last column in the list is deprecated. " 2396 "Instead, pass it as a separate argument, e.g. 'HistoND(model, cols, weightCol)'."); 2397 2398 if (!wName.empty() || nCols == hDims + 1) 2399 h->Sumw2(); 2400 2401 if (nCols != hDims + 1 && nCols != hDims) 2402 throw std::invalid_argument("Wrong number of columns for the specified number of histogram axes."); 2403 } 2404 2405 if (!wName.empty()) { 2406 // The action helper will invoke THnBase::Fill overload that performs weighted filling in case the number of 2407 // passed arguments is one more the number of dimensions of the histogram. 2408 ColumnNames_t userColumns = columnList; 2409 userColumns.push_back(std::string{wName}); 2410 return CreateAction<RDFInternal::ActionTags::HistoND, RDFDetail::RInferredType>(userColumns, h, h, fProxiedPtr, 2411 userColumns.size()); 2412 } 2413 return CreateAction<RDFInternal::ActionTags::HistoND, RDFDetail::RInferredType>(columnList, h, h, fProxiedPtr, 2414 columnList.size()); 2415 } 2416 2417 //////////////////////////////////////////////////////////////////////////// 2418 /// \brief Fill and return a sparse N-dimensional histogram (*lazy action*). 2419 /// \tparam FirstColumn The first type of the column the values of which are used to fill the object. Inferred if not 2420 /// present. 2421 /// \tparam OtherColumns A list of the other types of the columns the values of which are used to fill the 2422 /// object. 2423 /// \param[in] model The returned histogram will be constructed using this as a model. 2424 /// \param[in] columnList 2425 /// A list containing the names of the columns that will be passed when calling `Fill`. 2426 /// \param[in] wName The name of the column that will provide the weights. 2427 /// \return the N-dimensional histogram wrapped in a RResultPtr. 2428 /// 2429 /// This action is *lazy*: upon invocation of this method the calculation is 2430 /// booked but not executed. See RResultPtr documentation. 2431 /// 2432 /// ### Example usage: 2433 /// ~~~{.cpp} 2434 /// auto myFilledObj = myDf.HistoNSparseD<float, float, float, float>({"name","title", 4, 2435 /// {40,40,40,40}, {20.,20.,20.,20.}, {60.,60.,60.,60.}}, 2436 /// {"col0", "col1", "col2", "col3"}); 2437 /// ~~~ 2438 /// 2439 /// \note A column with event weights should not be passed as part of `columnList`, but instead be passed in the new 2440 /// argument `wName`: `HistoND(model, cols, weightCol)`. 2441 /// 2442 template <typename FirstColumn, typename... OtherColumns> // need FirstColumn to disambiguate overloads 2443 RResultPtr<::THnSparseD> 2444 HistoNSparseD(const THnSparseDModel &model, const ColumnNames_t &columnList, std::string_view wName = "") 2445 { 2446 std::shared_ptr<::THnSparseD> h(nullptr); 2447 { 2448 ROOT::Internal::RDF::RIgnoreErrorLevelRAII iel(kError); 2449 h = model.GetHistogram(); 2450 const auto hDims = h->GetNdimensions(); 2451 decltype(hDims) nCols = columnList.size(); 2452 2453 if (!wName.empty() && nCols == hDims + 1) 2454 throw std::invalid_argument("The weight column was passed as an argument and at the same time the list of " 2455 "input columns contains one column more than the number of dimensions of the " 2456 "histogram. Call as 'HistoNSparseD(model, cols, weightCol)'."); 2457 2458 if (nCols == hDims + 1) 2459 Warning("HistoNSparseD", 2460 "Passing the column with the weights as the last column in the list is deprecated. " 2461 "Instead, pass it as a separate argument, e.g. 'HistoNSparseD(model, cols, weightCol)'."); 2462 2463 if (!wName.empty() || nCols == hDims + 1) 2464 h->Sumw2(); 2465 2466 if (nCols != hDims + 1 && nCols != hDims) 2467 throw std::invalid_argument("Wrong number of columns for the specified number of histogram axes."); 2468 } 2469 2470 if (!wName.empty()) { 2471 // The action helper will invoke THnBase::Fill overload that performs weighted filling in case the number of 2472 // passed arguments is one more the number of dimensions of the histogram. 2473 ColumnNames_t userColumns = columnList; 2474 userColumns.push_back(std::string{wName}); 2475 return CreateAction<RDFInternal::ActionTags::HistoNSparseD, FirstColumn, OtherColumns...>(userColumns, h, h, 2476 fProxiedPtr); 2477 } 2478 return CreateAction<RDFInternal::ActionTags::HistoNSparseD, FirstColumn, OtherColumns...>(columnList, h, h, 2479 fProxiedPtr); 2480 } 2481 2482 //////////////////////////////////////////////////////////////////////////// 2483 /// \brief Fill and return a sparse N-dimensional histogram (*lazy action*). 2484 /// \param[in] model The returned histogram will be constructed using this as a model. 2485 /// \param[in] columnList A list containing the names of the columns that will be passed when calling `Fill` 2486 /// \param[in] wName The name of the column that will provide the weights. 2487 /// \return the N-dimensional histogram wrapped in a RResultPtr. 2488 /// 2489 /// This action is *lazy*: upon invocation of this method the calculation is 2490 /// booked but not executed. Also see RResultPtr. 2491 /// 2492 /// ### Example usage: 2493 /// ~~~{.cpp} 2494 /// auto myFilledObj = myDf.HistoNSparseD({"name","title", 4, 2495 /// {40,40,40,40}, {20.,20.,20.,20.}, {60.,60.,60.,60.}}, 2496 /// {"col0", "col1", "col2", "col3"}); 2497 /// ~~~ 2498 /// 2499 /// \note A column with event weights should not be passed as part of `columnList`, but instead be passed in the new 2500 /// argument `wName`: `HistoND(model, cols, weightCol)`. 2501 /// 2502 RResultPtr<::THnSparseD> 2503 HistoNSparseD(const THnSparseDModel &model, const ColumnNames_t &columnList, std::string_view wName = "") 2504 { 2505 std::shared_ptr<::THnSparseD> h(nullptr); 2506 { 2507 ROOT::Internal::RDF::RIgnoreErrorLevelRAII iel(kError); 2508 h = model.GetHistogram(); 2509 const auto hDims = h->GetNdimensions(); 2510 decltype(hDims) nCols = columnList.size(); 2511 2512 if (!wName.empty() && nCols == hDims + 1) 2513 throw std::invalid_argument("The weight column was passed as an argument and at the same time the list of " 2514 "input columns contains one column more than the number of dimensions of the " 2515 "histogram. Call as 'HistoNSparseD(model, cols, weightCol)'."); 2516 2517 if (nCols == hDims + 1) 2518 Warning("HistoNSparseD", 2519 "Passing the column with the weights as the last column in the list is deprecated. " 2520 "Instead, pass it as a separate argument, e.g. 'HistoNSparseD(model, cols, weightCol)'."); 2521 2522 if (!wName.empty() || nCols == hDims + 1) 2523 h->Sumw2(); 2524 2525 if (nCols != hDims + 1 && nCols != hDims) 2526 throw std::invalid_argument("Wrong number of columns for the specified number of histogram axes."); 2527 } 2528 2529 if (!wName.empty()) { 2530 // The action helper will invoke THnBase::Fill overload that performs weighted filling in case the number of 2531 // passed arguments is one more the number of dimensions of the histogram. 2532 ColumnNames_t userColumns = columnList; 2533 userColumns.push_back(std::string{wName}); 2534 return CreateAction<RDFInternal::ActionTags::HistoNSparseD, RDFDetail::RInferredType>( 2535 userColumns, h, h, fProxiedPtr, userColumns.size()); 2536 } 2537 return CreateAction<RDFInternal::ActionTags::HistoNSparseD, RDFDetail::RInferredType>( 2538 columnList, h, h, fProxiedPtr, columnList.size()); 2539 } 2540 2541 #ifdef R__HAS_ROOT7 2542 //////////////////////////////////////////////////////////////////////////// 2543 /// \brief Fill and return a one-dimensional RHist (*lazy action*). 2544 /// \tparam BinContentType The bin content type of the returned RHist. 2545 /// \param[in] nNormalBins The returned histogram will be constructed using this number of normal bins. 2546 /// \param[in] interval The axis interval of the constructed histogram (lower end inclusive, upper end exclusive). 2547 /// \param[in] vName The name of the column that will fill the histogram. 2548 /// \return the histogram wrapped in a RResultPtr. 2549 /// 2550 /// This action is *lazy*: upon invocation of this method the calculation is 2551 /// booked but not executed. Also see RResultPtr. 2552 /// 2553 /// ### Example usage: 2554 /// ~~~{.cpp} 2555 /// auto myHist = myDf.Hist(10, {5, 15}, "col0"); 2556 /// ~~~ 2557 template <typename BinContentType = double, typename V = RDFDetail::RInferredType> 2558 RResultPtr<ROOT::Experimental::RHist<BinContentType>> 2559 Hist(std::uint64_t nNormalBins, std::pair<double, double> interval, std::string_view vName) 2560 { 2561 std::shared_ptr h = std::make_shared<ROOT::Experimental::RHist<BinContentType>>(nNormalBins, interval); 2562 2563 const ColumnNames_t columnList = {std::string(vName)}; 2564 2565 return Hist<V>(h, columnList); 2566 } 2567 2568 //////////////////////////////////////////////////////////////////////////// 2569 /// \brief Fill and return an RHist (*lazy action*). 2570 /// \tparam BinContentType The bin content type of the returned RHist. 2571 /// \param[in] axes The returned histogram will be constructed using these axes. 2572 /// \param[in] columnList A list containing the names of the columns that will be passed when calling `Fill` 2573 /// \return the histogram wrapped in a RResultPtr. 2574 /// 2575 /// This action is *lazy*: upon invocation of this method the calculation is 2576 /// booked but not executed. Also see RResultPtr. 2577 /// 2578 /// ### Example usage: 2579 /// ~~~{.cpp} 2580 /// ROOT::Experimental::RRegularAxis axis(10, {5.0, 15.0}); 2581 /// auto myHist = myDf.Hist({axis}, {"col0"}); 2582 /// ~~~ 2583 template <typename BinContentType = double, typename ColumnType = RDFDetail::RInferredType, typename... ColumnTypes> 2584 RResultPtr<ROOT::Experimental::RHist<BinContentType>> 2585 Hist(std::vector<ROOT::Experimental::RAxisVariant> axes, const ColumnNames_t &columnList) 2586 { 2587 if (axes.size() != columnList.size()) { 2588 std::string msg = "Wrong number of columns for the specified number of histogram axes: "; 2589 msg += "expected " + std::to_string(axes.size()) + ", got " + std::to_string(columnList.size()); 2590 throw std::invalid_argument(msg); 2591 } 2592 2593 std::shared_ptr h = std::make_shared<ROOT::Experimental::RHist<BinContentType>>(std::move(axes)); 2594 2595 return Hist<ColumnType, ColumnTypes...>(h, columnList); 2596 } 2597 2598 //////////////////////////////////////////////////////////////////////////// 2599 /// \brief Fill the provided RHist (*lazy action*). 2600 /// \param[in] h The histogram that should be filled. 2601 /// \param[in] columnList A list containing the names of the columns that will be passed when calling `Fill` 2602 /// \return the histogram wrapped in a RResultPtr. 2603 /// 2604 /// This action is *lazy*: upon invocation of this method the calculation is 2605 /// booked but not executed. Also see RResultPtr. 2606 /// 2607 /// During execution of the computation graph, the passed histogram must only be accessed with methods that are 2608 /// allowed during concurrent filling. 2609 /// 2610 /// ### Example usage: 2611 /// ~~~{.cpp} 2612 /// auto h = std::make_shared<ROOT::Experimental::RHist<double>>(10, {5.0, 15.0}); 2613 /// auto myHist = myDf.Hist(h, {"col0"}); 2614 /// ~~~ 2615 template <typename ColumnType = RDFDetail::RInferredType, typename... ColumnTypes, typename BinContentType> 2616 RResultPtr<ROOT::Experimental::RHist<BinContentType>> 2617 Hist(std::shared_ptr<ROOT::Experimental::RHist<BinContentType>> h, const ColumnNames_t &columnList) 2618 { 2619 RDFInternal::WarnHist(); 2620 2621 if (h->GetNDimensions() != columnList.size()) { 2622 std::string msg = "Wrong number of columns for the passed histogram: "; 2623 msg += "expected " + std::to_string(h->GetNDimensions()) + ", got " + std::to_string(columnList.size()); 2624 throw std::invalid_argument(msg); 2625 } 2626 2627 return CreateAction<RDFInternal::ActionTags::Hist, ColumnType, ColumnTypes...>(columnList, h, h, fProxiedPtr, 2628 columnList.size()); 2629 } 2630 2631 //////////////////////////////////////////////////////////////////////////// 2632 /// \brief Fill and return a one-dimensional RHist with weights (*lazy action*). 2633 /// \tparam BinContentType The bin content type of the returned RHist. 2634 /// \param[in] nNormalBins The returned histogram will be constructed using this number of normal bins. 2635 /// \param[in] interval The axis interval of the constructed histogram (lower end inclusive, upper end exclusive). 2636 /// \param[in] vName The name of the column that will fill the histogram. 2637 /// \param[in] wName The name of the column that will provide the weights. 2638 /// \return the histogram wrapped in a RResultPtr. 2639 /// 2640 /// This action is *lazy*: upon invocation of this method the calculation is 2641 /// booked but not executed. Also see RResultPtr. 2642 /// 2643 /// ### Example usage: 2644 /// ~~~{.cpp} 2645 /// auto myHist = myDf.Hist(10, {5, 15}, "col0", "colW"); 2646 /// ~~~ 2647 template <typename BinContentType = ROOT::Experimental::RBinWithError, typename V = RDFDetail::RInferredType, 2648 typename W = RDFDetail::RInferredType> 2649 RResultPtr<ROOT::Experimental::RHist<BinContentType>> 2650 Hist(std::uint64_t nNormalBins, std::pair<double, double> interval, std::string_view vName, std::string_view wName) 2651 { 2652 std::shared_ptr h = std::make_shared<ROOT::Experimental::RHist<BinContentType>>(nNormalBins, interval); 2653 2654 const ColumnNames_t columnList = {std::string(vName)}; 2655 2656 return Hist<V, W>(h, columnList, wName); 2657 } 2658 2659 //////////////////////////////////////////////////////////////////////////// 2660 /// \brief Fill and return an RHist with weights (*lazy action*). 2661 /// \tparam BinContentType The bin content type of the returned RHist. 2662 /// \param[in] axes The returned histogram will be constructed using these axes. 2663 /// \param[in] columnList A list containing the names of the columns that will be passed when calling `Fill` 2664 /// \param[in] wName The name of the column that will provide the weights. 2665 /// \return the histogram wrapped in a RResultPtr. 2666 /// 2667 /// This action is *lazy*: upon invocation of this method the calculation is 2668 /// booked but not executed. Also see RResultPtr. 2669 /// 2670 /// This overload is not available for integral bin content types (see \ref RHistEngine::SupportsWeightedFilling). 2671 /// 2672 /// ### Example usage: 2673 /// ~~~{.cpp} 2674 /// ROOT::Experimental::RRegularAxis axis(10, {5.0, 15.0}); 2675 /// auto myHist = myDf.Hist({axis}, {"col0"}, "colW"); 2676 /// ~~~ 2677 template <typename BinContentType = ROOT::Experimental::RBinWithError, 2678 typename ColumnType = RDFDetail::RInferredType, typename... ColumnTypes> 2679 RResultPtr<ROOT::Experimental::RHist<BinContentType>> 2680 Hist(std::vector<ROOT::Experimental::RAxisVariant> axes, const ColumnNames_t &columnList, std::string_view wName) 2681 { 2682 static_assert(ROOT::Experimental::RHistEngine<BinContentType>::SupportsWeightedFilling, 2683 "weighted filling is not supported for integral bin content types"); 2684 2685 if (axes.size() != columnList.size()) { 2686 std::string msg = "Wrong number of columns for the specified number of histogram axes: "; 2687 msg += "expected " + std::to_string(axes.size()) + ", got " + std::to_string(columnList.size()); 2688 throw std::invalid_argument(msg); 2689 } 2690 2691 std::shared_ptr h = std::make_shared<ROOT::Experimental::RHist<BinContentType>>(std::move(axes)); 2692 2693 return Hist<ColumnType, ColumnTypes...>(h, columnList, wName); 2694 } 2695 2696 //////////////////////////////////////////////////////////////////////////// 2697 /// \brief Fill the provided RHist with weights (*lazy action*). 2698 /// \param[in] h The histogram that should be filled. 2699 /// \param[in] columnList A list containing the names of the columns that will be passed when calling `Fill` 2700 /// \param[in] wName The name of the column that will provide the weights. 2701 /// \return the histogram wrapped in a RResultPtr. 2702 /// 2703 /// This action is *lazy*: upon invocation of this method the calculation is 2704 /// booked but not executed. Also see RResultPtr. 2705 /// 2706 /// This overload is not available for integral bin content types (see \ref RHistEngine::SupportsWeightedFilling). 2707 /// 2708 /// During execution of the computation graph, the passed histogram must only be accessed with methods that are 2709 /// allowed during concurrent filling. 2710 /// 2711 /// ### Example usage: 2712 /// ~~~{.cpp} 2713 /// auto h = std::make_shared<ROOT::Experimental::RHist<double>>(10, {5.0, 15.0}); 2714 /// auto myHist = myDf.Hist(h, {"col0"}, "colW"); 2715 /// ~~~ 2716 template <typename ColumnType = RDFDetail::RInferredType, typename... ColumnTypes, typename BinContentType> 2717 RResultPtr<ROOT::Experimental::RHist<BinContentType>> 2718 Hist(std::shared_ptr<ROOT::Experimental::RHist<BinContentType>> h, const ColumnNames_t &columnList, 2719 std::string_view wName) 2720 { 2721 static_assert(ROOT::Experimental::RHistEngine<BinContentType>::SupportsWeightedFilling, 2722 "weighted filling is not supported for integral bin content types"); 2723 2724 RDFInternal::WarnHist(); 2725 2726 if (h->GetNDimensions() != columnList.size()) { 2727 std::string msg = "Wrong number of columns for the passed histogram: "; 2728 msg += "expected " + std::to_string(h->GetNDimensions()) + ", got " + std::to_string(columnList.size()); 2729 throw std::invalid_argument(msg); 2730 } 2731 2732 // Add the weight column to the list of argument columns to pass it through the infrastructure. 2733 ColumnNames_t columnListWithWeights(columnList); 2734 columnListWithWeights.push_back(std::string(wName)); 2735 2736 return CreateAction<RDFInternal::ActionTags::HistWithWeight, ColumnType, ColumnTypes...>( 2737 columnListWithWeights, h, h, fProxiedPtr, columnListWithWeights.size()); 2738 } 2739 2740 //////////////////////////////////////////////////////////////////////////// 2741 /// \brief Fill the provided RHistEngine (*lazy action*). 2742 /// \param[in] h The histogram that should be filled. 2743 /// \param[in] columnList A list containing the names of the columns that will be passed when calling `Fill` 2744 /// \return the histogram wrapped in a RResultPtr. 2745 /// 2746 /// This action is *lazy*: upon invocation of this method the calculation is 2747 /// booked but not executed. Also see RResultPtr. 2748 /// 2749 /// During execution of the computation graph, the passed histogram must only be accessed with methods that are 2750 /// allowed during concurrent filling. 2751 /// 2752 /// ### Example usage: 2753 /// ~~~{.cpp} 2754 /// auto h = std::make_shared<ROOT::Experimental::RHistEngine<double>>(10, {5.0, 15.0}); 2755 /// auto myHist = myDf.Hist(h, {"col0"}); 2756 /// ~~~ 2757 template <typename ColumnType = RDFDetail::RInferredType, typename... ColumnTypes, typename BinContentType> 2758 RResultPtr<ROOT::Experimental::RHistEngine<BinContentType>> 2759 Hist(std::shared_ptr<ROOT::Experimental::RHistEngine<BinContentType>> h, const ColumnNames_t &columnList) 2760 { 2761 RDFInternal::WarnHist(); 2762 2763 if (h->GetNDimensions() != columnList.size()) { 2764 std::string msg = "Wrong number of columns for the passed histogram: "; 2765 msg += "expected " + std::to_string(h->GetNDimensions()) + ", got " + std::to_string(columnList.size()); 2766 throw std::invalid_argument(msg); 2767 } 2768 2769 return CreateAction<RDFInternal::ActionTags::Hist, ColumnType, ColumnTypes...>(columnList, h, h, fProxiedPtr, 2770 columnList.size()); 2771 } 2772 2773 //////////////////////////////////////////////////////////////////////////// 2774 /// \brief Fill the provided RHistEngine with weights (*lazy action*). 2775 /// \param[in] h The histogram that should be filled. 2776 /// \param[in] columnList A list containing the names of the columns that will be passed when calling `Fill` 2777 /// \param[in] wName The name of the column that will provide the weights. 2778 /// \return the histogram wrapped in a RResultPtr. 2779 /// 2780 /// This action is *lazy*: upon invocation of this method the calculation is 2781 /// booked but not executed. Also see RResultPtr. 2782 /// 2783 /// This overload is not available for integral bin content types (see \ref RHistEngine::SupportsWeightedFilling). 2784 /// 2785 /// During execution of the computation graph, the passed histogram must only be accessed with methods that are 2786 /// allowed during concurrent filling. 2787 /// 2788 /// ### Example usage: 2789 /// ~~~{.cpp} 2790 /// auto h = std::make_shared<ROOT::Experimental::RHistEngine<double>>(10, {5.0, 15.0}); 2791 /// auto myHist = myDf.Hist(h, {"col0"}, "colW"); 2792 /// ~~~ 2793 template <typename ColumnType = RDFDetail::RInferredType, typename... ColumnTypes, typename BinContentType> 2794 RResultPtr<ROOT::Experimental::RHistEngine<BinContentType>> 2795 Hist(std::shared_ptr<ROOT::Experimental::RHistEngine<BinContentType>> h, const ColumnNames_t &columnList, 2796 std::string_view wName) 2797 { 2798 static_assert(ROOT::Experimental::RHistEngine<BinContentType>::SupportsWeightedFilling, 2799 "weighted filling is not supported for integral bin content types"); 2800 2801 RDFInternal::WarnHist(); 2802 2803 if (h->GetNDimensions() != columnList.size()) { 2804 std::string msg = "Wrong number of columns for the passed histogram: "; 2805 msg += "expected " + std::to_string(h->GetNDimensions()) + ", got " + std::to_string(columnList.size()); 2806 throw std::invalid_argument(msg); 2807 } 2808 2809 // Add the weight column to the list of argument columns to pass it through the infrastructure. 2810 ColumnNames_t columnListWithWeights(columnList); 2811 columnListWithWeights.push_back(std::string(wName)); 2812 2813 return CreateAction<RDFInternal::ActionTags::HistWithWeight, ColumnType, ColumnTypes...>( 2814 columnListWithWeights, h, h, fProxiedPtr, columnListWithWeights.size()); 2815 } 2816 #endif 2817 2818 //////////////////////////////////////////////////////////////////////////// 2819 /// \brief Fill and return a TGraph object (*lazy action*). 2820 /// \tparam X The type of the column used to fill the x axis. 2821 /// \tparam Y The type of the column used to fill the y axis. 2822 /// \param[in] x The name of the column that will fill the x axis. 2823 /// \param[in] y The name of the column that will fill the y axis. 2824 /// \return the TGraph wrapped in a RResultPtr. 2825 /// 2826 /// Columns can be of a container type (e.g. std::vector<double>), in which case the TGraph 2827 /// is filled with each one of the elements of the container. 2828 /// If Multithreading is enabled, the order in which points are inserted is undefined. 2829 /// If the Graph has to be drawn, it is suggested to the user to sort it on the x before printing. 2830 /// A name and a title to the TGraph is given based on the input column names. 2831 /// 2832 /// This action is *lazy*: upon invocation of this method the calculation is 2833 /// booked but not executed. Also see RResultPtr. 2834 /// 2835 /// ### Example usage: 2836 /// ~~~{.cpp} 2837 /// // Deduce column types (this invocation needs jitting internally) 2838 /// auto myGraph1 = myDf.Graph("xValues", "yValues"); 2839 /// // Explicit column types 2840 /// auto myGraph2 = myDf.Graph<int, float>("xValues", "yValues"); 2841 /// ~~~ 2842 /// 2843 /// \note Differently from other ROOT interfaces, the returned TGraph is not associated to gDirectory 2844 /// and the caller is responsible for its lifetime (in particular, a typical source of confusion is that 2845 /// if result histograms go out of scope before the end of the program, ROOT might display a blank canvas). 2846 template <typename X = RDFDetail::RInferredType, typename Y = RDFDetail::RInferredType> 2847 RResultPtr<::TGraph> Graph(std::string_view x = "", std::string_view y = "") 2848 { 2849 auto graph = std::make_shared<::TGraph>(); 2850 const std::vector<std::string_view> columnViews = {x, y}; 2851 const auto userColumns = RDFInternal::AtLeastOneEmptyString(columnViews) 2852 ? ColumnNames_t() 2853 : ColumnNames_t(columnViews.begin(), columnViews.end()); 2854 2855 const auto validatedColumns = GetValidatedColumnNames(2, userColumns); 2856 2857 // We build a default name and title based on the input columns 2858 const auto g_name = validatedColumns[1] + "_vs_" + validatedColumns[0]; 2859 const auto g_title = validatedColumns[1] + " vs " + validatedColumns[0]; 2860 graph->SetNameTitle(g_name.c_str(), g_title.c_str()); 2861 graph->GetXaxis()->SetTitle(validatedColumns[0].c_str()); 2862 graph->GetYaxis()->SetTitle(validatedColumns[1].c_str()); 2863 2864 return CreateAction<RDFInternal::ActionTags::Graph, X, Y>(validatedColumns, graph, graph, fProxiedPtr); 2865 } 2866 2867 //////////////////////////////////////////////////////////////////////////// 2868 /// \brief Fill and return a TGraphAsymmErrors object (*lazy action*). 2869 /// \param[in] x The name of the column that will fill the x axis. 2870 /// \param[in] y The name of the column that will fill the y axis. 2871 /// \param[in] exl The name of the column of X low errors 2872 /// \param[in] exh The name of the column of X high errors 2873 /// \param[in] eyl The name of the column of Y low errors 2874 /// \param[in] eyh The name of the column of Y high errors 2875 /// \return the TGraphAsymmErrors wrapped in a RResultPtr. 2876 /// 2877 /// Columns can be of a container type (e.g. std::vector<double>), in which case the graph 2878 /// is filled with each one of the elements of the container. 2879 /// If Multithreading is enabled, the order in which points are inserted is undefined. 2880 /// 2881 /// This action is *lazy*: upon invocation of this method the calculation is 2882 /// booked but not executed. Also see RResultPtr. 2883 /// 2884 /// ### Example usage: 2885 /// ~~~{.cpp} 2886 /// // Deduce column types (this invocation needs jitting internally) 2887 /// auto myGAE1 = myDf.GraphAsymmErrors("xValues", "yValues", "exl", "exh", "eyl", "eyh"); 2888 /// // Explicit column types 2889 /// using f = float 2890 /// auto myGAE2 = myDf.GraphAsymmErrors<f, f, f, f, f, f>("xValues", "yValues", "exl", "exh", "eyl", "eyh"); 2891 /// ~~~ 2892 /// 2893 /// `GraphAsymmErrors` should also be used for the cases in which values associated only with 2894 /// one of the axes have associated errors. For example, only `ey` exist and `ex` are equal to zero. 2895 /// In such cases, user should do the following: 2896 /// ~~~{.cpp} 2897 /// // Create a column of zeros in RDataFrame 2898 /// auto rdf_withzeros = rdf.Define("zero", "0"); 2899 /// // or alternatively: 2900 /// auto rdf_withzeros = rdf.Define("zero", []() -> double { return 0.;}); 2901 /// // Create the graph with y errors only 2902 /// auto rdf_errorsOnYOnly = rdf_withzeros.GraphAsymmErrors("xValues", "yValues", "zero", "zero", "eyl", "eyh"); 2903 /// ~~~ 2904 /// 2905 /// \note Differently from other ROOT interfaces, the returned TGraphAsymmErrors is not associated to gDirectory 2906 /// and the caller is responsible for its lifetime (in particular, a typical source of confusion is that 2907 /// if result histograms go out of scope before the end of the program, ROOT might display a blank canvas). 2908 template <typename X = RDFDetail::RInferredType, typename Y = RDFDetail::RInferredType, 2909 typename EXL = RDFDetail::RInferredType, typename EXH = RDFDetail::RInferredType, 2910 typename EYL = RDFDetail::RInferredType, typename EYH = RDFDetail::RInferredType> 2911 RResultPtr<::TGraphAsymmErrors> 2912 GraphAsymmErrors(std::string_view x = "", std::string_view y = "", std::string_view exl = "", 2913 std::string_view exh = "", std::string_view eyl = "", std::string_view eyh = "") 2914 { 2915 auto graph = std::make_shared<::TGraphAsymmErrors>(); 2916 const std::vector<std::string_view> columnViews = {x, y, exl, exh, eyl, eyh}; 2917 const auto userColumns = RDFInternal::AtLeastOneEmptyString(columnViews) 2918 ? ColumnNames_t() 2919 : ColumnNames_t(columnViews.begin(), columnViews.end()); 2920 2921 const auto validatedColumns = GetValidatedColumnNames(6, userColumns); 2922 2923 // We build a default name and title based on the input columns 2924 const auto g_name = validatedColumns[1] + "_vs_" + validatedColumns[0]; 2925 const auto g_title = validatedColumns[1] + " vs " + validatedColumns[0]; 2926 graph->SetNameTitle(g_name.c_str(), g_title.c_str()); 2927 graph->GetXaxis()->SetTitle(validatedColumns[0].c_str()); 2928 graph->GetYaxis()->SetTitle(validatedColumns[1].c_str()); 2929 2930 return CreateAction<RDFInternal::ActionTags::GraphAsymmErrors, X, Y, EXL, EXH, EYL, EYH>(validatedColumns, graph, 2931 graph, fProxiedPtr); 2932 } 2933 2934 //////////////////////////////////////////////////////////////////////////// 2935 /// \brief Fill and return a one-dimensional profile (*lazy action*). 2936 /// \tparam V1 The type of the column the values of which are used to fill the profile. Inferred if not present. 2937 /// \tparam V2 The type of the column the values of which are used to fill the profile. Inferred if not present. 2938 /// \param[in] model The model to be considered to build the new return value. 2939 /// \param[in] v1Name The name of the column that will fill the x axis. 2940 /// \param[in] v2Name The name of the column that will fill the y axis. 2941 /// \return the monodimensional profile wrapped in a RResultPtr. 2942 /// 2943 /// This action is *lazy*: upon invocation of this method the calculation is 2944 /// booked but not executed. Also see RResultPtr. 2945 /// 2946 /// ### Example usage: 2947 /// ~~~{.cpp} 2948 /// // Deduce column types (this invocation needs jitting internally) 2949 /// auto myProf1 = myDf.Profile1D({"profName", "profTitle", 64u, -4., 4.}, "xValues", "yValues"); 2950 /// // Explicit column types 2951 /// auto myProf2 = myDf.Graph<int, float>({"profName", "profTitle", 64u, -4., 4.}, "xValues", "yValues"); 2952 /// ~~~ 2953 /// 2954 /// \note Differently from other ROOT interfaces, the returned profile is not associated to gDirectory 2955 /// and the caller is responsible for its lifetime (in particular, a typical source of confusion is that 2956 /// if result histograms go out of scope before the end of the program, ROOT might display a blank canvas). 2957 template <typename V1 = RDFDetail::RInferredType, typename V2 = RDFDetail::RInferredType> 2958 RResultPtr<::TProfile> 2959 Profile1D(const TProfile1DModel &model, std::string_view v1Name = "", std::string_view v2Name = "") 2960 { 2961 std::shared_ptr<::TProfile> h(nullptr); 2962 { 2963 ROOT::Internal::RDF::RIgnoreErrorLevelRAII iel(kError); 2964 h = model.GetProfile(); 2965 } 2966 2967 if (!RDFInternal::HistoUtils<::TProfile>::HasAxisLimits(*h)) { 2968 throw std::runtime_error("Profiles with no axes limits are not supported yet."); 2969 } 2970 const std::vector<std::string_view> columnViews = {v1Name, v2Name}; 2971 const auto userColumns = RDFInternal::AtLeastOneEmptyString(columnViews) 2972 ? ColumnNames_t() 2973 : ColumnNames_t(columnViews.begin(), columnViews.end()); 2974 return CreateAction<RDFInternal::ActionTags::Profile1D, V1, V2>(userColumns, h, h, fProxiedPtr); 2975 } 2976 2977 //////////////////////////////////////////////////////////////////////////// 2978 /// \brief Fill and return a one-dimensional profile (*lazy action*). 2979 /// \tparam V1 The type of the column the values of which are used to fill the profile. Inferred if not present. 2980 /// \tparam V2 The type of the column the values of which are used to fill the profile. Inferred if not present. 2981 /// \tparam W The type of the column the weights of which are used to fill the profile. Inferred if not present. 2982 /// \param[in] model The model to be considered to build the new return value. 2983 /// \param[in] v1Name The name of the column that will fill the x axis. 2984 /// \param[in] v2Name The name of the column that will fill the y axis. 2985 /// \param[in] wName The name of the column that will provide the weights. 2986 /// \return the monodimensional profile wrapped in a RResultPtr. 2987 /// 2988 /// This action is *lazy*: upon invocation of this method the calculation is 2989 /// booked but not executed. Also see RResultPtr. 2990 /// 2991 /// ### Example usage: 2992 /// ~~~{.cpp} 2993 /// // Deduce column types (this invocation needs jitting internally) 2994 /// auto myProf1 = myDf.Profile1D({"profName", "profTitle", 64u, -4., 4.}, "xValues", "yValues", "weight"); 2995 /// // Explicit column types 2996 /// auto myProf2 = myDf.Profile1D<int, float, double>({"profName", "profTitle", 64u, -4., 4.}, 2997 /// "xValues", "yValues", "weight"); 2998 /// ~~~ 2999 /// 3000 /// See the first Profile1D() overload for more details. 3001 template <typename V1 = RDFDetail::RInferredType, typename V2 = RDFDetail::RInferredType, 3002 typename W = RDFDetail::RInferredType> 3003 RResultPtr<::TProfile> 3004 Profile1D(const TProfile1DModel &model, std::string_view v1Name, std::string_view v2Name, std::string_view wName) 3005 { 3006 std::shared_ptr<::TProfile> h(nullptr); 3007 { 3008 ROOT::Internal::RDF::RIgnoreErrorLevelRAII iel(kError); 3009 h = model.GetProfile(); 3010 } 3011 3012 if (!RDFInternal::HistoUtils<::TProfile>::HasAxisLimits(*h)) { 3013 throw std::runtime_error("Profile histograms with no axes limits are not supported yet."); 3014 } 3015 const std::vector<std::string_view> columnViews = {v1Name, v2Name, wName}; 3016 const auto userColumns = RDFInternal::AtLeastOneEmptyString(columnViews) 3017 ? ColumnNames_t() 3018 : ColumnNames_t(columnViews.begin(), columnViews.end()); 3019 return CreateAction<RDFInternal::ActionTags::Profile1D, V1, V2, W>(userColumns, h, h, fProxiedPtr); 3020 } 3021 3022 //////////////////////////////////////////////////////////////////////////// 3023 /// \brief Fill and return a one-dimensional profile (*lazy action*). 3024 /// See the first Profile1D() overload for more details. 3025 template <typename V1, typename V2, typename W> 3026 RResultPtr<::TProfile> Profile1D(const TProfile1DModel &model) 3027 { 3028 return Profile1D<V1, V2, W>(model, "", "", ""); 3029 } 3030 3031 //////////////////////////////////////////////////////////////////////////// 3032 /// \brief Fill and return a two-dimensional profile (*lazy action*). 3033 /// \tparam V1 The type of the column used to fill the x axis of the histogram. Inferred if not present. 3034 /// \tparam V2 The type of the column used to fill the y axis of the histogram. Inferred if not present. 3035 /// \tparam V3 The type of the column used to fill the z axis of the histogram. Inferred if not present. 3036 /// \param[in] model The returned profile will be constructed using this as a model. 3037 /// \param[in] v1Name The name of the column that will fill the x axis. 3038 /// \param[in] v2Name The name of the column that will fill the y axis. 3039 /// \param[in] v3Name The name of the column that will fill the z axis. 3040 /// \return the bidimensional profile wrapped in a RResultPtr. 3041 /// 3042 /// This action is *lazy*: upon invocation of this method the calculation is 3043 /// booked but not executed. Also see RResultPtr. 3044 /// 3045 /// ### Example usage: 3046 /// ~~~{.cpp} 3047 /// // Deduce column types (this invocation needs jitting internally) 3048 /// auto myProf1 = myDf.Profile2D({"profName", "profTitle", 40, -4, 4, 40, -4, 4, 0, 20}, 3049 /// "xValues", "yValues", "zValues"); 3050 /// // Explicit column types 3051 /// auto myProf2 = myDf.Profile2D<int, float, double>({"profName", "profTitle", 40, -4, 4, 40, -4, 4, 0, 20}, 3052 /// "xValues", "yValues", "zValues"); 3053 /// ~~~ 3054 /// 3055 /// \note Differently from other ROOT interfaces, the returned profile is not associated to gDirectory 3056 /// and the caller is responsible for its lifetime (in particular, a typical source of confusion is that 3057 /// if result histograms go out of scope before the end of the program, ROOT might display a blank canvas). 3058 template <typename V1 = RDFDetail::RInferredType, typename V2 = RDFDetail::RInferredType, 3059 typename V3 = RDFDetail::RInferredType> 3060 RResultPtr<::TProfile2D> Profile2D(const TProfile2DModel &model, std::string_view v1Name = "", 3061 std::string_view v2Name = "", std::string_view v3Name = "") 3062 { 3063 std::shared_ptr<::TProfile2D> h(nullptr); 3064 { 3065 ROOT::Internal::RDF::RIgnoreErrorLevelRAII iel(kError); 3066 h = model.GetProfile(); 3067 } 3068 3069 if (!RDFInternal::HistoUtils<::TProfile2D>::HasAxisLimits(*h)) { 3070 throw std::runtime_error("2D profiles with no axes limits are not supported yet."); 3071 } 3072 const std::vector<std::string_view> columnViews = {v1Name, v2Name, v3Name}; 3073 const auto userColumns = RDFInternal::AtLeastOneEmptyString(columnViews) 3074 ? ColumnNames_t() 3075 : ColumnNames_t(columnViews.begin(), columnViews.end()); 3076 return CreateAction<RDFInternal::ActionTags::Profile2D, V1, V2, V3>(userColumns, h, h, fProxiedPtr); 3077 } 3078 3079 //////////////////////////////////////////////////////////////////////////// 3080 /// \brief Fill and return a two-dimensional profile (*lazy action*). 3081 /// \tparam V1 The type of the column used to fill the x axis of the histogram. Inferred if not present. 3082 /// \tparam V2 The type of the column used to fill the y axis of the histogram. Inferred if not present. 3083 /// \tparam V3 The type of the column used to fill the z axis of the histogram. Inferred if not present. 3084 /// \tparam W The type of the column used for the weights of the histogram. Inferred if not present. 3085 /// \param[in] model The returned histogram will be constructed using this as a model. 3086 /// \param[in] v1Name The name of the column that will fill the x axis. 3087 /// \param[in] v2Name The name of the column that will fill the y axis. 3088 /// \param[in] v3Name The name of the column that will fill the z axis. 3089 /// \param[in] wName The name of the column that will provide the weights. 3090 /// \return the bidimensional profile wrapped in a RResultPtr. 3091 /// 3092 /// This action is *lazy*: upon invocation of this method the calculation is 3093 /// booked but not executed. Also see RResultPtr. 3094 /// 3095 /// ### Example usage: 3096 /// ~~~{.cpp} 3097 /// // Deduce column types (this invocation needs jitting internally) 3098 /// auto myProf1 = myDf.Profile2D({"profName", "profTitle", 40, -4, 4, 40, -4, 4, 0, 20}, 3099 /// "xValues", "yValues", "zValues", "weight"); 3100 /// // Explicit column types 3101 /// auto myProf2 = myDf.Profile2D<int, float, double, int>({"profName", "profTitle", 40, -4, 4, 40, -4, 4, 0, 20}, 3102 /// "xValues", "yValues", "zValues", "weight"); 3103 /// ~~~ 3104 /// 3105 /// See the first Profile2D() overload for more details. 3106 template <typename V1 = RDFDetail::RInferredType, typename V2 = RDFDetail::RInferredType, 3107 typename V3 = RDFDetail::RInferredType, typename W = RDFDetail::RInferredType> 3108 RResultPtr<::TProfile2D> Profile2D(const TProfile2DModel &model, std::string_view v1Name, std::string_view v2Name, 3109 std::string_view v3Name, std::string_view wName) 3110 { 3111 std::shared_ptr<::TProfile2D> h(nullptr); 3112 { 3113 ROOT::Internal::RDF::RIgnoreErrorLevelRAII iel(kError); 3114 h = model.GetProfile(); 3115 } 3116 3117 if (!RDFInternal::HistoUtils<::TProfile2D>::HasAxisLimits(*h)) { 3118 throw std::runtime_error("2D profiles with no axes limits are not supported yet."); 3119 } 3120 const std::vector<std::string_view> columnViews = {v1Name, v2Name, v3Name, wName}; 3121 const auto userColumns = RDFInternal::AtLeastOneEmptyString(columnViews) 3122 ? ColumnNames_t() 3123 : ColumnNames_t(columnViews.begin(), columnViews.end()); 3124 return CreateAction<RDFInternal::ActionTags::Profile2D, V1, V2, V3, W>(userColumns, h, h, fProxiedPtr); 3125 } 3126 3127 /// \brief Fill and return a two-dimensional profile (*lazy action*). 3128 /// See the first Profile2D() overload for more details. 3129 template <typename V1, typename V2, typename V3, typename W> 3130 RResultPtr<::TProfile2D> Profile2D(const TProfile2DModel &model) 3131 { 3132 return Profile2D<V1, V2, V3, W>(model, "", "", "", ""); 3133 } 3134 3135 //////////////////////////////////////////////////////////////////////////// 3136 /// \brief Return an object of type T on which `T::Fill` will be called once per event (*lazy action*). 3137 /// 3138 /// Type T must provide at least: 3139 /// - a copy-constructor 3140 /// - a `Fill` method that accepts as many arguments and with same types as the column names passed as columnList 3141 /// (these types can also be passed as template parameters to this method) 3142 /// - a `Merge` method with signature `Merge(TCollection *)` or `Merge(const std::vector<T *>&)` that merges the 3143 /// objects passed as argument into the object on which `Merge` was called (an analogous of TH1::Merge). Note that 3144 /// if the signature that takes a `TCollection*` is used, then T must inherit from TObject (to allow insertion in 3145 /// the TCollection*). 3146 /// 3147 /// \tparam FirstColumn The first type of the column the values of which are used to fill the object. Inferred together with OtherColumns if not present. 3148 /// \tparam OtherColumns A list of the other types of the columns the values of which are used to fill the object. 3149 /// \tparam T The type of the object to fill. Automatically deduced. 3150 /// \param[in] model The model to be considered to build the new return value. 3151 /// \param[in] columnList A list containing the names of the columns that will be passed when calling `Fill` 3152 /// \return the filled object wrapped in a RResultPtr. 3153 /// 3154 /// The user gives up ownership of the model object. 3155 /// The list of column names to be used for filling must always be specified. 3156 /// This action is *lazy*: upon invocation of this method the calculation is booked but not executed. 3157 /// Also see RResultPtr. 3158 /// 3159 /// ### Example usage: 3160 /// ~~~{.cpp} 3161 /// MyClass obj; 3162 /// // Deduce column types (this invocation needs jitting internally, and in this case 3163 /// // MyClass needs to be known to the interpreter) 3164 /// auto myFilledObj = myDf.Fill(obj, {"col0", "col1"}); 3165 /// // explicit column types 3166 /// auto myFilledObj = myDf.Fill<float, float>(obj, {"col0", "col1"}); 3167 /// ~~~ 3168 /// 3169 template <typename FirstColumn = RDFDetail::RInferredType, typename... OtherColumns, typename T> 3170 RResultPtr<std::decay_t<T>> Fill(T &&model, const ColumnNames_t &columnList) 3171 { 3172 auto h = std::make_shared<std::decay_t<T>>(std::forward<T>(model)); 3173 if (!RDFInternal::HistoUtils<T>::HasAxisLimits(*h)) { 3174 throw std::runtime_error("The absence of axes limits is not supported yet."); 3175 } 3176 return CreateAction<RDFInternal::ActionTags::Fill, FirstColumn, OtherColumns...>(columnList, h, h, fProxiedPtr, 3177 columnList.size()); 3178 } 3179 3180 //////////////////////////////////////////////////////////////////////////// 3181 /// \brief Return a TStatistic object, filled once per event (*lazy action*). 3182 /// 3183 /// \tparam V The type of the value column 3184 /// \param[in] value The name of the column with the values to fill the statistics with. 3185 /// \return the filled TStatistic object wrapped in a RResultPtr. 3186 /// 3187 /// ### Example usage: 3188 /// ~~~{.cpp} 3189 /// // Deduce column type (this invocation needs jitting internally) 3190 /// auto stats0 = myDf.Stats("values"); 3191 /// // Explicit column type 3192 /// auto stats1 = myDf.Stats<float>("values"); 3193 /// ~~~ 3194 /// 3195 template <typename V = RDFDetail::RInferredType> 3196 RResultPtr<TStatistic> Stats(std::string_view value = "") 3197 { 3198 ColumnNames_t columns; 3199 if (!value.empty()) { 3200 columns.emplace_back(std::string(value)); 3201 } 3202 const auto validColumnNames = GetValidatedColumnNames(1, columns); 3203 if (std::is_same<V, RDFDetail::RInferredType>::value) { 3204 return Fill(TStatistic(), validColumnNames); 3205 } else { 3206 return Fill<V>(TStatistic(), validColumnNames); 3207 } 3208 } 3209 3210 //////////////////////////////////////////////////////////////////////////// 3211 /// \brief Return a TStatistic object, filled once per event (*lazy action*). 3212 /// 3213 /// \tparam V The type of the value column 3214 /// \tparam W The type of the weight column 3215 /// \param[in] value The name of the column with the values to fill the statistics with. 3216 /// \param[in] weight The name of the column with the weights to fill the statistics with. 3217 /// \return the filled TStatistic object wrapped in a RResultPtr. 3218 /// 3219 /// ### Example usage: 3220 /// ~~~{.cpp} 3221 /// // Deduce column types (this invocation needs jitting internally) 3222 /// auto stats0 = myDf.Stats("values", "weights"); 3223 /// // Explicit column types 3224 /// auto stats1 = myDf.Stats<int, float>("values", "weights"); 3225 /// ~~~ 3226 /// 3227 template <typename V = RDFDetail::RInferredType, typename W = RDFDetail::RInferredType> 3228 RResultPtr<TStatistic> Stats(std::string_view value, std::string_view weight) 3229 { 3230 ColumnNames_t columns{std::string(value), std::string(weight)}; 3231 constexpr auto vIsInferred = std::is_same<V, RDFDetail::RInferredType>::value; 3232 constexpr auto wIsInferred = std::is_same<W, RDFDetail::RInferredType>::value; 3233 const auto validColumnNames = GetValidatedColumnNames(2, columns); 3234 // We have 3 cases: 3235 // 1. Both types are inferred: we use Fill and let the jit kick in. 3236 // 2. One of the two types is explicit and the other one is inferred: the case is not supported. 3237 // 3. Both types are explicit: we invoke the fully compiled Fill method. 3238 if (vIsInferred && wIsInferred) { 3239 return Fill(TStatistic(), validColumnNames); 3240 } else if (vIsInferred != wIsInferred) { 3241 std::string error("The "); 3242 error += vIsInferred ? "value " : "weight "; 3243 error += "column type is explicit, while the "; 3244 error += vIsInferred ? "weight " : "value "; 3245 error += " is specified to be inferred. This case is not supported: please specify both types or none."; 3246 throw std::runtime_error(error); 3247 } else { 3248 return Fill<V, W>(TStatistic(), validColumnNames); 3249 } 3250 } 3251 3252 //////////////////////////////////////////////////////////////////////////// 3253 /// \brief Return the minimum of processed column values (*lazy action*). 3254 /// \tparam T The type of the branch/column. 3255 /// \param[in] columnName The name of the branch/column to be treated. 3256 /// \return the minimum value of the selected column wrapped in a RResultPtr. 3257 /// 3258 /// If T is not specified, RDataFrame will infer it from the data and just-in-time compile the correct 3259 /// template specialization of this method. 3260 /// If the type of the column is inferred, the return type is `double`, the type of the column otherwise. 3261 /// 3262 /// This action is *lazy*: upon invocation of this method the calculation is 3263 /// booked but not executed. Also see RResultPtr. 3264 /// 3265 /// ### Example usage: 3266 /// ~~~{.cpp} 3267 /// // Deduce column type (this invocation needs jitting internally) 3268 /// auto minVal0 = myDf.Min("values"); 3269 /// // Explicit column type 3270 /// auto minVal1 = myDf.Min<double>("values"); 3271 /// ~~~ 3272 /// 3273 template <typename T = RDFDetail::RInferredType> 3274 RResultPtr<RDFDetail::MinReturnType_t<T>> Min(std::string_view columnName = "") 3275 { 3276 const auto userColumns = columnName.empty() ? ColumnNames_t() : ColumnNames_t({std::string(columnName)}); 3277 using RetType_t = RDFDetail::MinReturnType_t<T>; 3278 auto minV = std::make_shared<RetType_t>(std::numeric_limits<RetType_t>::max()); 3279 return CreateAction<RDFInternal::ActionTags::Min, T>(userColumns, minV, minV, fProxiedPtr); 3280 } 3281 3282 //////////////////////////////////////////////////////////////////////////// 3283 /// \brief Return the maximum of processed column values (*lazy action*). 3284 /// \tparam T The type of the branch/column. 3285 /// \param[in] columnName The name of the branch/column to be treated. 3286 /// \return the maximum value of the selected column wrapped in a RResultPtr. 3287 /// 3288 /// If T is not specified, RDataFrame will infer it from the data and just-in-time compile the correct 3289 /// template specialization of this method. 3290 /// If the type of the column is inferred, the return type is `double`, the type of the column otherwise. 3291 /// 3292 /// This action is *lazy*: upon invocation of this method the calculation is 3293 /// booked but not executed. Also see RResultPtr. 3294 /// 3295 /// ### Example usage: 3296 /// ~~~{.cpp} 3297 /// // Deduce column type (this invocation needs jitting internally) 3298 /// auto maxVal0 = myDf.Max("values"); 3299 /// // Explicit column type 3300 /// auto maxVal1 = myDf.Max<double>("values"); 3301 /// ~~~ 3302 /// 3303 template <typename T = RDFDetail::RInferredType> 3304 RResultPtr<RDFDetail::MaxReturnType_t<T>> Max(std::string_view columnName = "") 3305 { 3306 const auto userColumns = columnName.empty() ? ColumnNames_t() : ColumnNames_t({std::string(columnName)}); 3307 using RetType_t = RDFDetail::MaxReturnType_t<T>; 3308 auto maxV = std::make_shared<RetType_t>(std::numeric_limits<RetType_t>::lowest()); 3309 return CreateAction<RDFInternal::ActionTags::Max, T>(userColumns, maxV, maxV, fProxiedPtr); 3310 } 3311 3312 //////////////////////////////////////////////////////////////////////////// 3313 /// \brief Return the mean of processed column values (*lazy action*). 3314 /// \tparam T The type of the branch/column. 3315 /// \param[in] columnName The name of the branch/column to be treated. 3316 /// \return the mean value of the selected column wrapped in a RResultPtr. 3317 /// 3318 /// If T is not specified, RDataFrame will infer it from the data and just-in-time compile the correct 3319 /// template specialization of this method. 3320 /// Note that internally, the summations are executed with Kahan sums in double precision, irrespective 3321 /// of the type of column that is read. 3322 /// 3323 /// This action is *lazy*: upon invocation of this method the calculation is 3324 /// booked but not executed. Also see RResultPtr. 3325 /// 3326 /// ### Example usage: 3327 /// ~~~{.cpp} 3328 /// // Deduce column type (this invocation needs jitting internally) 3329 /// auto meanVal0 = myDf.Mean("values"); 3330 /// // Explicit column type 3331 /// auto meanVal1 = myDf.Mean<double>("values"); 3332 /// ~~~ 3333 /// 3334 template <typename T = RDFDetail::RInferredType> 3335 RResultPtr<double> Mean(std::string_view columnName = "") 3336 { 3337 const auto userColumns = columnName.empty() ? ColumnNames_t() : ColumnNames_t({std::string(columnName)}); 3338 auto meanV = std::make_shared<double>(0); 3339 return CreateAction<RDFInternal::ActionTags::Mean, T>(userColumns, meanV, meanV, fProxiedPtr); 3340 } 3341 3342 //////////////////////////////////////////////////////////////////////////// 3343 /// \brief Return the unbiased standard deviation of processed column values (*lazy action*). 3344 /// \tparam T The type of the branch/column. 3345 /// \param[in] columnName The name of the branch/column to be treated. 3346 /// \return the standard deviation value of the selected column wrapped in a RResultPtr. 3347 /// 3348 /// If T is not specified, RDataFrame will infer it from the data and just-in-time compile the correct 3349 /// template specialization of this method. 3350 /// 3351 /// This action is *lazy*: upon invocation of this method the calculation is 3352 /// booked but not executed. Also see RResultPtr. 3353 /// 3354 /// ### Example usage: 3355 /// ~~~{.cpp} 3356 /// // Deduce column type (this invocation needs jitting internally) 3357 /// auto stdDev0 = myDf.StdDev("values"); 3358 /// // Explicit column type 3359 /// auto stdDev1 = myDf.StdDev<double>("values"); 3360 /// ~~~ 3361 /// 3362 template <typename T = RDFDetail::RInferredType> 3363 RResultPtr<double> StdDev(std::string_view columnName = "") 3364 { 3365 const auto userColumns = columnName.empty() ? ColumnNames_t() : ColumnNames_t({std::string(columnName)}); 3366 auto stdDeviationV = std::make_shared<double>(0); 3367 return CreateAction<RDFInternal::ActionTags::StdDev, T>(userColumns, stdDeviationV, stdDeviationV, fProxiedPtr); 3368 } 3369 3370 // clang-format off 3371 //////////////////////////////////////////////////////////////////////////// 3372 /// \brief Return the sum of processed column values (*lazy action*). 3373 /// \tparam T The type of the branch/column. 3374 /// \param[in] columnName The name of the branch/column. 3375 /// \param[in] initValue Optional initial value for the sum. If not present, the column values must be default-constructible. 3376 /// \return the sum of the selected column wrapped in a RResultPtr. 3377 /// 3378 /// If T is not specified, RDataFrame will infer it from the data and just-in-time compile the correct 3379 /// template specialization of this method. 3380 /// If the type of the column is inferred, the return type is `double`, the type of the column otherwise. 3381 /// 3382 /// This action is *lazy*: upon invocation of this method the calculation is 3383 /// booked but not executed. Also see RResultPtr. 3384 /// 3385 /// ### Example usage: 3386 /// ~~~{.cpp} 3387 /// // Deduce column type (this invocation needs jitting internally) 3388 /// auto sum0 = myDf.Sum("values"); 3389 /// // Explicit column type 3390 /// auto sum1 = myDf.Sum<double>("values"); 3391 /// ~~~ 3392 /// 3393 template <typename T = RDFDetail::RInferredType> 3394 RResultPtr<RDFDetail::SumReturnType_t<T>> 3395 Sum(std::string_view columnName = "", 3396 const RDFDetail::SumReturnType_t<T> &initValue = RDFDetail::SumReturnType_t<T>{}) 3397 { 3398 const auto userColumns = columnName.empty() ? ColumnNames_t() : ColumnNames_t({std::string(columnName)}); 3399 auto sumV = std::make_shared<RDFDetail::SumReturnType_t<T>>(initValue); 3400 return CreateAction<RDFInternal::ActionTags::Sum, T>(userColumns, sumV, sumV, fProxiedPtr); 3401 } 3402 // clang-format on 3403 3404 //////////////////////////////////////////////////////////////////////////// 3405 /// \brief Gather filtering statistics. 3406 /// \return the resulting `RCutFlowReport` instance wrapped in a RResultPtr. 3407 /// 3408 /// Calling `Report` on the main `RDataFrame` object gathers stats for 3409 /// all named filters in the call graph. Calling this method on a 3410 /// stored chain state (i.e. a graph node different from the first) gathers 3411 /// the stats for all named filters in the chain section between the original 3412 /// `RDataFrame` and that node (included). Stats are gathered in the same 3413 /// order as the named filters have been added to the graph. 3414 /// A RResultPtr<RCutFlowReport> is returned to allow inspection of the 3415 /// effects cuts had. 3416 /// 3417 /// This action is *lazy*: upon invocation of 3418 /// this method the calculation is booked but not executed. See RResultPtr 3419 /// documentation. 3420 /// 3421 /// ### Example usage: 3422 /// ~~~{.cpp} 3423 /// auto filtered = d.Filter(cut1, {"b1"}, "Cut1").Filter(cut2, {"b2"}, "Cut2"); 3424 /// auto cutReport = filtered3.Report(); 3425 /// cutReport->Print(); 3426 /// ~~~ 3427 /// 3428 RResultPtr<RCutFlowReport> Report() 3429 { 3430 bool returnEmptyReport = false; 3431 // if this is a RInterface<RLoopManager> on which `Define` has been called, users 3432 // are calling `Report` on a chain of the form LoopManager->Define->Define->..., which 3433 // certainly does not contain named filters. 3434 // The number 4 takes into account the implicit columns for entry and slot number 3435 // and their aliases (2 + 2, i.e. {r,t}dfentry_ and {r,t}dfslot_) 3436 if (std::is_same<Proxied, RLoopManager>::value && fColRegister.GenerateColumnNames().size() > 4) 3437 returnEmptyReport = true; 3438 3439 auto rep = std::make_shared<RCutFlowReport>(); 3440 using Helper_t = RDFInternal::ReportHelper<Proxied>; 3441 using Action_t = RDFInternal::RAction<Helper_t, Proxied>; 3442 3443 auto action = std::make_unique<Action_t>(Helper_t(rep, fProxiedPtr.get(), returnEmptyReport), ColumnNames_t({}), 3444 fProxiedPtr, RDFInternal::RColumnRegister(fColRegister)); 3445 3446 return MakeResultPtr(rep, *fLoopManager, std::move(action)); 3447 } 3448 3449 /// \brief Returns the names of the filters created. 3450 /// \return the container of filters names. 3451 /// 3452 /// If called on a root node, all the filters in the computation graph will 3453 /// be printed. For any other node, only the filters upstream of that node. 3454 /// Filters without a name are printed as "Unnamed Filter" 3455 /// This is not an action nor a transformation, just a query to the RDataFrame object. 3456 /// 3457 /// ### Example usage: 3458 /// ~~~{.cpp} 3459 /// auto filtNames = d.GetFilterNames(); 3460 /// for (auto &&filtName : filtNames) std::cout << filtName << std::endl; 3461 /// ~~~ 3462 /// 3463 std::vector<std::string> GetFilterNames() { return RDFInternal::GetFilterNames(fProxiedPtr); } 3464 3465 // clang-format off 3466 //////////////////////////////////////////////////////////////////////////// 3467 /// \brief Execute a user-defined accumulation operation on the processed column values in each processing slot. 3468 /// \tparam F The type of the aggregator callable. Automatically deduced. 3469 /// \tparam U The type of the aggregator variable. Must be default-constructible, copy-constructible and copy-assignable. Automatically deduced. 3470 /// \tparam T The type of the column to apply the reduction to. Automatically deduced. 3471 /// \param[in] aggregator A callable with signature `U(U,T)` or `void(U&,T)`, where T is the type of the column, U is the type of the aggregator variable 3472 /// \param[in] merger A callable with signature `U(U,U)` or `void(std::vector<U>&)` used to merge the results of the accumulations of each thread 3473 /// \param[in] columnName The column to be aggregated. If omitted, the first default column is used instead. 3474 /// \param[in] aggIdentity The aggregator variable of each thread is initialized to this value (or is default-constructed if the parameter is omitted) 3475 /// \return the result of the aggregation wrapped in a RResultPtr. 3476 /// 3477 /// An aggregator callable takes two values, an aggregator variable and a column value. The aggregator variable is 3478 /// initialized to aggIdentity or default-constructed if aggIdentity is omitted. 3479 /// This action calls the aggregator callable for each processed entry, passing in the aggregator variable and 3480 /// the value of the column columnName. 3481 /// If the signature is `U(U,T)` the aggregator variable is then copy-assigned the result of the execution of the callable. 3482 /// Otherwise the signature of aggregator must be `void(U&,T)`. 3483 /// 3484 /// The merger callable is used to merge the partial accumulation results of each processing thread. It is only called in multi-thread executions. 3485 /// If its signature is `U(U,U)` the aggregator variables of each thread are merged two by two. 3486 /// If its signature is `void(std::vector<U>& a)` it is assumed that it merges all aggregators in a[0]. 3487 /// 3488 /// This action is *lazy*: upon invocation of this method the calculation is booked but not executed. Also see RResultPtr. 3489 /// 3490 /// Example usage: 3491 /// ~~~{.cpp} 3492 /// auto aggregator = [](double acc, double x) { return acc * x; }; 3493 /// ROOT::EnableImplicitMT(); 3494 /// // If multithread is enabled, the aggregator function will be called by more threads 3495 /// // and will produce a vector of partial accumulators. 3496 /// // The merger function performs the final aggregation of these partial results. 3497 /// auto merger = [](std::vector<double> &accumulators) { 3498 /// for (auto i : ROOT::TSeqU(1u, accumulators.size())) { 3499 /// accumulators[0] *= accumulators[i]; 3500 /// } 3501 /// }; 3502 /// 3503 /// // The accumulator is initialized at this value by every thread. 3504 /// double initValue = 1.; 3505 /// 3506 /// // Multiplies all elements of the column "x" 3507 /// auto result = d.Aggregate(aggregator, merger, "x", initValue); 3508 /// ~~~ 3509 // clang-format on 3510 template <typename AccFun, typename MergeFun, typename R = typename TTraits::CallableTraits<AccFun>::ret_type, 3511 typename ArgTypes = typename TTraits::CallableTraits<AccFun>::arg_types, 3512 typename ArgTypesNoDecay = typename TTraits::CallableTraits<AccFun>::arg_types_nodecay, 3513 typename U = TTraits::TakeFirstParameter_t<ArgTypes>, 3514 typename T = TTraits::TakeFirstParameter_t<TTraits::RemoveFirstParameter_t<ArgTypes>>> 3515 RResultPtr<U> Aggregate(AccFun aggregator, MergeFun merger, std::string_view columnName, const U &aggIdentity) 3516 { 3517 RDFInternal::CheckAggregate<R, MergeFun>(ArgTypesNoDecay()); 3518 const auto columns = columnName.empty() ? ColumnNames_t() : ColumnNames_t({std::string(columnName)}); 3519 3520 const auto validColumnNames = GetValidatedColumnNames(1, columns); 3521 CheckAndFillDSColumns(validColumnNames, TTraits::TypeList<T>()); 3522 3523 auto accObjPtr = std::make_shared<U>(aggIdentity); 3524 using Helper_t = RDFInternal::AggregateHelper<AccFun, MergeFun, R, T, U>; 3525 using Action_t = RDFInternal::RAction<Helper_t, Proxied>; 3526 auto action = std::make_unique<Action_t>( 3527 Helper_t(std::move(aggregator), std::move(merger), accObjPtr, fLoopManager->GetNSlots()), validColumnNames, 3528 fProxiedPtr, fColRegister); 3529 return MakeResultPtr(accObjPtr, *fLoopManager, std::move(action)); 3530 } 3531 3532 // clang-format off 3533 //////////////////////////////////////////////////////////////////////////// 3534 /// \brief Execute a user-defined accumulation operation on the processed column values in each processing slot. 3535 /// \tparam F The type of the aggregator callable. Automatically deduced. 3536 /// \tparam U The type of the aggregator variable. Must be default-constructible, copy-constructible and copy-assignable. Automatically deduced. 3537 /// \tparam T The type of the column to apply the reduction to. Automatically deduced. 3538 /// \param[in] aggregator A callable with signature `U(U,T)` or `void(U,T)`, where T is the type of the column, U is the type of the aggregator variable 3539 /// \param[in] merger A callable with signature `U(U,U)` or `void(std::vector<U>&)` used to merge the results of the accumulations of each thread 3540 /// \param[in] columnName The column to be aggregated. If omitted, the first default column is used instead. 3541 /// \return the result of the aggregation wrapped in a RResultPtr. 3542 /// 3543 /// See previous Aggregate overload for more information. 3544 // clang-format on 3545 template <typename AccFun, typename MergeFun, typename R = typename TTraits::CallableTraits<AccFun>::ret_type, 3546 typename ArgTypes = typename TTraits::CallableTraits<AccFun>::arg_types, 3547 typename U = TTraits::TakeFirstParameter_t<ArgTypes>, 3548 typename T = TTraits::TakeFirstParameter_t<TTraits::RemoveFirstParameter_t<ArgTypes>>> 3549 RResultPtr<U> Aggregate(AccFun aggregator, MergeFun merger, std::string_view columnName = "") 3550 { 3551 static_assert( 3552 std::is_default_constructible<U>::value, 3553 "aggregated object cannot be default-constructed. Please provide an initialisation value (aggIdentity)"); 3554 return Aggregate(std::move(aggregator), std::move(merger), columnName, U()); 3555 } 3556 3557 // clang-format off 3558 //////////////////////////////////////////////////////////////////////////// 3559 /// \brief Book execution of a custom action using a user-defined helper object. 3560 /// \tparam FirstColumn The type of the first column used by this action. Inferred together with OtherColumns if not present. 3561 /// \tparam OtherColumns A list of the types of the other columns used by this action 3562 /// \tparam Helper The type of the user-defined helper. See below for the required interface it should expose. 3563 /// \param[in] helper The Action Helper to be scheduled. 3564 /// \param[in] columns The names of the columns on which the helper acts. 3565 /// \return the result of the helper wrapped in a RResultPtr. 3566 /// 3567 /// This method books a custom action for execution. The behavior of the action is completely dependent on the 3568 /// Helper object provided by the caller. The required interface for the helper is described below (more 3569 /// methods that the ones required can be present, e.g. a constructor that takes the number of worker threads is usually useful): 3570 /// 3571 /// ### Mandatory interface 3572 /// 3573 /// * `Helper` must publicly inherit from `ROOT::Detail::RDF::RActionImpl<Helper>` 3574 /// * `Helper::Result_t`: public alias for the type of the result of this action helper. `Result_t` must be default-constructible. 3575 /// * `Helper(Helper &&)`: a move-constructor is required. Copy-constructors are discouraged. 3576 /// * `std::shared_ptr<Result_t> GetResultPtr() const`: return a shared_ptr to the result of this action (of type 3577 /// Result_t). The RResultPtr returned by Book will point to this object. Note that this method can be called 3578 /// _before_ Initialize(), because the RResultPtr is constructed before the event loop is started. 3579 /// * `void Initialize()`: this method is called once before starting the event-loop. Useful for setup operations. 3580 /// It must reset the state of the helper to the expected state at the beginning of the event loop: the same helper, 3581 /// or copies of it, might be used for multiple event loops (e.g. in the presence of systematic variations). 3582 /// * `void InitTask(TTreeReader *, unsigned int slot)`: each working thread shall call this method during the event 3583 /// loop, before processing a batch of entries. The pointer passed as argument, if not null, will point to the TTreeReader 3584 /// that RDataFrame has set up to read the task's batch of entries. It is passed to the helper to allow certain advanced optimizations 3585 /// it should not usually serve any purpose for the Helper. This method is often no-op for simple helpers. 3586 /// * `void Exec(unsigned int slot, ColumnTypes...columnValues)`: each working thread shall call this method 3587 /// during the event-loop, possibly concurrently. No two threads will ever call Exec with the same 'slot' value: 3588 /// this parameter is there to facilitate writing thread-safe helpers. The other arguments will be the values of 3589 /// the requested columns for the particular entry being processed. 3590 /// * `void Finalize()`: this method is called at the end of the event loop. Commonly used to finalize the contents of the result. 3591 /// * `std::string GetActionName()`: it returns a string identifier for this type of action that RDataFrame will use in 3592 /// diagnostics, SaveGraph(), etc. 3593 /// 3594 /// ### Optional methods 3595 /// 3596 /// If these methods are implemented they enable extra functionality as per the description below. 3597 /// 3598 /// * `Result_t &PartialUpdate(unsigned int slot)`: if present, it must return the value of the partial result of this action for the given 'slot'. 3599 /// Different threads might call this method concurrently, but will do so with different 'slot' numbers. 3600 /// RDataFrame leverages this method to implement RResultPtr::OnPartialResult(). 3601 /// * `ROOT::RDF::SampleCallback_t GetSampleCallback()`: if present, it must return a callable with the 3602 /// appropriate signature (see ROOT::RDF::SampleCallback_t) that will be invoked at the beginning of the processing 3603 /// of every sample, as in DefinePerSample(). 3604 /// * `Helper MakeNew(void *newResult, std::string_view variation = "nominal")`: if implemented, it enables varying 3605 /// the action's result with VariationsFor(). It takes a type-erased new result that can be safely cast to a 3606 /// `std::shared_ptr<Result_t> *` (a pointer to shared pointer) and should be used as the action's output result. 3607 /// The function optionally takes the name of the current variation which could be useful in customizing its behaviour. 3608 /// 3609 /// In case Book is called without specifying column types as template arguments, corresponding typed code will be just-in-time compiled 3610 /// by RDataFrame. In that case the Helper class needs to be known to the ROOT interpreter. 3611 /// 3612 /// This action is *lazy*: upon invocation of this method the calculation is booked but not executed. Also see RResultPtr. 3613 /// 3614 /// ### Examples 3615 /// See [this tutorial](https://root.cern/doc/master/df018__customActions_8C.html) for an example implementation of an action helper. 3616 /// 3617 /// It is also possible to inspect the code used by built-in RDataFrame actions at ActionHelpers.hxx. 3618 /// 3619 // clang-format on 3620 template <typename FirstColumn = RDFDetail::RInferredType, typename... OtherColumns, typename Helper> 3621 RResultPtr<typename std::decay_t<Helper>::Result_t> Book(Helper &&helper, const ColumnNames_t &columns = {}) 3622 { 3623 using HelperT = std::decay_t<Helper>; 3624 // TODO add more static sanity checks on Helper 3625 using AH = RDFDetail::RActionImpl<HelperT>; 3626 static_assert(std::is_base_of<AH, HelperT>::value && std::is_convertible<HelperT *, AH *>::value, 3627 "Action helper of type T must publicly inherit from ROOT::Detail::RDF::RActionImpl<T>"); 3628 3629 auto hPtr = std::make_shared<HelperT>(std::forward<Helper>(helper)); 3630 auto resPtr = hPtr->GetResultPtr(); 3631 3632 if (std::is_same<FirstColumn, RDFDetail::RInferredType>::value && columns.empty()) { 3633 return CallCreateActionWithoutColsIfPossible<HelperT>(resPtr, hPtr, TTraits::TypeList<FirstColumn>{}); 3634 } else { 3635 return CreateAction<RDFInternal::ActionTags::Book, FirstColumn, OtherColumns...>(columns, resPtr, hPtr, 3636 fProxiedPtr, columns.size()); 3637 } 3638 } 3639 3640 //////////////////////////////////////////////////////////////////////////// 3641 /// \brief Provides a representation of the columns in the dataset. 3642 /// \tparam ColumnTypes variadic list of branch/column types. 3643 /// \param[in] columnList Names of the columns to be displayed. 3644 /// \param[in] nRows Number of events for each column to be displayed. 3645 /// \param[in] nMaxCollectionElements Maximum number of collection elements to display per row. 3646 /// \return the `RDisplay` instance wrapped in a RResultPtr. 3647 /// 3648 /// This function returns a `RResultPtr<RDisplay>` containing all the entries to be displayed, organized in a tabular 3649 /// form. RDisplay will either print on the standard output a summarized version through `RDisplay::Print()` or will 3650 /// return a complete version through `RDisplay::AsString()`. 3651 /// 3652 /// This action is *lazy*: upon invocation of this method the calculation is booked but not executed. Also see 3653 /// RResultPtr. 3654 /// 3655 /// Example usage: 3656 /// ~~~{.cpp} 3657 /// // Preparing the RResultPtr<RDisplay> object with all columns and default number of entries 3658 /// auto d1 = rdf.Display(""); 3659 /// // Preparing the RResultPtr<RDisplay> object with two columns and 128 entries 3660 /// auto d2 = d.Display({"x", "y"}, 128); 3661 /// // Printing the short representations, the event loop will run 3662 /// d1->Print(); 3663 /// d2->Print(); 3664 /// ~~~ 3665 template <typename... ColumnTypes> 3666 RResultPtr<RDisplay> Display(const ColumnNames_t &columnList, size_t nRows = 5, size_t nMaxCollectionElements = 10) 3667 { 3668 CheckIMTDisabled("Display"); 3669 auto newCols = columnList; 3670 newCols.insert(newCols.begin(), "rdfentry_"); // Artificially insert first column 3671 auto displayer = std::make_shared<RDisplay>(newCols, GetColumnTypeNamesList(newCols), nMaxCollectionElements); 3672 using displayHelperArgs_t = std::pair<size_t, std::shared_ptr<RDisplay>>; 3673 // Need to add ULong64_t type corresponding to the first column rdfentry_ 3674 return CreateAction<RDFInternal::ActionTags::Display, ULong64_t, ColumnTypes...>( 3675 std::move(newCols), displayer, std::make_shared<displayHelperArgs_t>(nRows, displayer), fProxiedPtr); 3676 } 3677 3678 //////////////////////////////////////////////////////////////////////////// 3679 /// \brief Provides a representation of the columns in the dataset. 3680 /// \param[in] columnList Names of the columns to be displayed. 3681 /// \param[in] nRows Number of events for each column to be displayed. 3682 /// \param[in] nMaxCollectionElements Maximum number of collection elements to display per row. 3683 /// \return the `RDisplay` instance wrapped in a RResultPtr. 3684 /// 3685 /// This overload automatically infers the column types. 3686 /// See the previous overloads for further details. 3687 /// 3688 /// Invoked when no types are specified to Display 3689 RResultPtr<RDisplay> Display(const ColumnNames_t &columnList, size_t nRows = 5, size_t nMaxCollectionElements = 10) 3690 { 3691 CheckIMTDisabled("Display"); 3692 auto newCols = columnList; 3693 newCols.insert(newCols.begin(), "rdfentry_"); // Artificially insert first column 3694 auto displayer = std::make_shared<RDisplay>(newCols, GetColumnTypeNamesList(newCols), nMaxCollectionElements); 3695 using displayHelperArgs_t = std::pair<size_t, std::shared_ptr<RDisplay>>; 3696 return CreateAction<RDFInternal::ActionTags::Display, RDFDetail::RInferredType>( 3697 std::move(newCols), displayer, std::make_shared<displayHelperArgs_t>(nRows, displayer), fProxiedPtr, 3698 columnList.size() + 1); 3699 } 3700 3701 //////////////////////////////////////////////////////////////////////////// 3702 /// \brief Provides a representation of the columns in the dataset. 3703 /// \param[in] columnNameRegexp A regular expression to select the columns. 3704 /// \param[in] nRows Number of events for each column to be displayed. 3705 /// \param[in] nMaxCollectionElements Maximum number of collection elements to display per row. 3706 /// \return the `RDisplay` instance wrapped in a RResultPtr. 3707 /// 3708 /// The existing columns are matched against the regular expression. If the string provided 3709 /// is empty, all columns are selected. 3710 /// See the previous overloads for further details. 3711 RResultPtr<RDisplay> 3712 Display(std::string_view columnNameRegexp = "", size_t nRows = 5, size_t nMaxCollectionElements = 10) 3713 { 3714 const auto columnNames = GetColumnNames(); 3715 const auto selectedColumns = RDFInternal::ConvertRegexToColumns(columnNames, columnNameRegexp, "Display"); 3716 return Display(selectedColumns, nRows, nMaxCollectionElements); 3717 } 3718 3719 //////////////////////////////////////////////////////////////////////////// 3720 /// \brief Provides a representation of the columns in the dataset. 3721 /// \param[in] columnList Names of the columns to be displayed. 3722 /// \param[in] nRows Number of events for each column to be displayed. 3723 /// \param[in] nMaxCollectionElements Number of maximum elements in collection. 3724 /// \return the `RDisplay` instance wrapped in a RResultPtr. 3725 /// 3726 /// See the previous overloads for further details. 3727 RResultPtr<RDisplay> 3728 Display(std::initializer_list<std::string> columnList, size_t nRows = 5, size_t nMaxCollectionElements = 10) 3729 { 3730 ColumnNames_t selectedColumns(columnList); 3731 return Display(selectedColumns, nRows, nMaxCollectionElements); 3732 } 3733 3734 private: 3735 template <typename F, typename DefineType, typename RetType = typename TTraits::CallableTraits<F>::ret_type> 3736 std::enable_if_t<std::is_default_constructible<RetType>::value, RInterface<Proxied>> 3737 DefineImpl(std::string_view name, F &&expression, const ColumnNames_t &columns, const std::string &where) 3738 { 3739 if (where.compare(0, 8, "Redefine") != 0) { // not a Redefine 3740 RDFInternal::CheckValidCppVarName(name, where); 3741 RDFInternal::CheckForRedefinition(where, name, fColRegister, 3742 GetDataSource() ? GetDataSource()->GetColumnNames() : ColumnNames_t{}); 3743 } else { 3744 RDFInternal::CheckForDefinition(where, name, fColRegister, 3745 GetDataSource() ? GetDataSource()->GetColumnNames() : ColumnNames_t{}); 3746 RDFInternal::CheckForNoVariations(where, name, fColRegister); 3747 } 3748 3749 using ArgTypes_t = typename TTraits::CallableTraits<F>::arg_types; 3750 using ColTypesTmp_t = typename RDFInternal::RemoveFirstParameterIf< 3751 std::is_same<DefineType, RDFDetail::ExtraArgsForDefine::Slot>::value, ArgTypes_t>::type; 3752 using ColTypes_t = typename RDFInternal::RemoveFirstTwoParametersIf< 3753 std::is_same<DefineType, RDFDetail::ExtraArgsForDefine::SlotAndEntry>::value, ColTypesTmp_t>::type; 3754 3755 constexpr auto nColumns = ColTypes_t::list_size; 3756 3757 const auto validColumnNames = GetValidatedColumnNames(nColumns, columns); 3758 CheckAndFillDSColumns(validColumnNames, ColTypes_t()); 3759 3760 // Declare return type to the interpreter, for future use by jitted actions 3761 auto retTypeName = RDFInternal::TypeID2TypeName(typeid(RetType)); 3762 if (retTypeName.empty()) { 3763 // The type is not known to the interpreter. 3764 // We must not error out here, but if/when this column is used in jitted code 3765 const auto demangledType = RDFInternal::DemangleTypeIdName(typeid(RetType)); 3766 retTypeName = "CLING_UNKNOWN_TYPE_" + demangledType; 3767 } 3768 3769 using NewCol_t = RDFDetail::RDefine<F, DefineType>; 3770 auto newColumn = std::make_shared<NewCol_t>(name, retTypeName, std::forward<F>(expression), validColumnNames, 3771 fColRegister, *fLoopManager); 3772 3773 RDFInternal::RColumnRegister newCols(fColRegister); 3774 newCols.AddDefine(std::move(newColumn)); 3775 3776 RInterface<Proxied> newInterface(fProxiedPtr, *fLoopManager, std::move(newCols)); 3777 3778 return newInterface; 3779 } 3780 3781 // This overload is chosen when the callable passed to Define or DefineSlot returns void. 3782 // It simply fires a compile-time error. This is preferable to a static_assert in the main `Define` overload because 3783 // this way compilation of `Define` has no way to continue after throwing the error. 3784 template <typename F, typename DefineType, typename RetType = typename TTraits::CallableTraits<F>::ret_type, 3785 bool IsFStringConv = std::is_convertible<F, std::string>::value, 3786 bool IsRetTypeDefConstr = std::is_default_constructible<RetType>::value> 3787 std::enable_if_t<!IsFStringConv && !IsRetTypeDefConstr, RInterface<Proxied>> 3788 DefineImpl(std::string_view, F, const ColumnNames_t &, const std::string &) 3789 { 3790 static_assert(std::is_default_constructible<typename TTraits::CallableTraits<F>::ret_type>::value, 3791 "Error in `Define`: type returned by expression is not default-constructible"); 3792 return *this; // never reached 3793 } 3794 3795 //////////////////////////////////////////////////////////////////////////// 3796 /// \brief Implementation of cache. 3797 template <typename... ColTypes, std::size_t... S> 3798 RInterface<RLoopManager> CacheImpl(const ColumnNames_t &columnList, std::index_sequence<S...>) 3799 { 3800 const auto columnListWithoutSizeColumns = RDFInternal::FilterArraySizeColNames(columnList, "Snapshot"); 3801 3802 // Check at compile time that the columns types are copy constructible 3803 constexpr bool areCopyConstructible = 3804 RDFInternal::TEvalAnd<std::is_copy_constructible<ColTypes>::value...>::value; 3805 static_assert(areCopyConstructible, "Columns of a type which is not copy constructible cannot be cached yet."); 3806 3807 RDFInternal::CheckTypesAndPars(sizeof...(ColTypes), columnListWithoutSizeColumns.size()); 3808 3809 auto colHolders = std::make_tuple(Take<ColTypes>(columnListWithoutSizeColumns[S])...); 3810 auto ds = std::make_unique<RLazyDS<ColTypes...>>( 3811 std::make_pair(columnListWithoutSizeColumns[S], std::get<S>(colHolders))...); 3812 3813 RInterface<RLoopManager> cachedRDF(std::make_shared<RLoopManager>(std::move(ds), columnListWithoutSizeColumns)); 3814 3815 return cachedRDF; 3816 } 3817 3818 template <bool IsSingleColumn, typename F> 3819 RInterface<Proxied> 3820 VaryImpl(const std::vector<std::string> &colNames, F &&expression, const ColumnNames_t &inputColumns, 3821 const std::vector<std::string> &variationTags, std::string_view variationName) 3822 { 3823 using F_t = std::decay_t<F>; 3824 using ColTypes_t = typename TTraits::CallableTraits<F_t>::arg_types; 3825 using RetType = typename TTraits::CallableTraits<F_t>::ret_type; 3826 constexpr auto nColumns = ColTypes_t::list_size; 3827 3828 SanityChecksForVary<RetType>(colNames, variationTags, variationName); 3829 3830 const auto validColumnNames = GetValidatedColumnNames(nColumns, inputColumns); 3831 CheckAndFillDSColumns(validColumnNames, ColTypes_t{}); 3832 3833 auto retTypeName = RDFInternal::TypeID2TypeName(typeid(RetType)); 3834 if (retTypeName.empty()) { 3835 // The type is not known to the interpreter, but we don't want to error out 3836 // here, rather if/when this column is used in jitted code, so we inject a broken but telling type name. 3837 const auto demangledType = RDFInternal::DemangleTypeIdName(typeid(RetType)); 3838 retTypeName = "CLING_UNKNOWN_TYPE_" + demangledType; 3839 } 3840 3841 auto variation = std::make_shared<RDFInternal::RVariation<F_t, IsSingleColumn>>( 3842 colNames, variationName, std::forward<F>(expression), variationTags, retTypeName, fColRegister, *fLoopManager, 3843 validColumnNames); 3844 3845 RDFInternal::RColumnRegister newCols(fColRegister); 3846 newCols.AddVariation(std::move(variation)); 3847 3848 RInterface<Proxied> newInterface(fProxiedPtr, *fLoopManager, std::move(newCols)); 3849 3850 return newInterface; 3851 } 3852 3853 RInterface<Proxied> JittedVaryImpl(const std::vector<std::string> &colNames, std::string_view expression, 3854 const std::vector<std::string> &variationTags, std::string_view variationName, 3855 bool isSingleColumn) 3856 { 3857 R__ASSERT(!variationTags.empty() && "Must have at least one variation."); 3858 R__ASSERT(!colNames.empty() && "Must have at least one varied column."); 3859 R__ASSERT(!variationName.empty() && "Must provide a variation name."); 3860 3861 for (auto &colName : colNames) { 3862 RDFInternal::CheckValidCppVarName(colName, "Vary"); 3863 RDFInternal::CheckForDefinition("Vary", colName, fColRegister, 3864 GetDataSource() ? GetDataSource()->GetColumnNames() : ColumnNames_t{}); 3865 } 3866 RDFInternal::CheckValidCppVarName(variationName, "Vary"); 3867 3868 // when varying multiple columns, they must be different columns 3869 if (colNames.size() > 1) { 3870 std::set<std::string> uniqueCols(colNames.begin(), colNames.end()); 3871 if (uniqueCols.size() != colNames.size()) 3872 throw std::logic_error("A column name was passed to the same Vary invocation multiple times."); 3873 } 3874 3875 // Cannot vary different input column types, assume the first 3876 auto varyColType = GetColumnType(colNames[0]); 3877 auto jittedVariation = 3878 RDFInternal::BookVariationJit(colNames, variationName, variationTags, expression, *fLoopManager, 3879 GetDataSource(), fColRegister, isSingleColumn, varyColType); 3880 3881 RDFInternal::RColumnRegister newColRegister(fColRegister); 3882 newColRegister.AddVariation(std::move(jittedVariation)); 3883 3884 RInterface<Proxied> newInterface(fProxiedPtr, *fLoopManager, std::move(newColRegister)); 3885 3886 return newInterface; 3887 } 3888 3889 template <typename Helper, typename ActionResultType> 3890 auto CallCreateActionWithoutColsIfPossible(const std::shared_ptr<ActionResultType> &resPtr, 3891 const std::shared_ptr<Helper> &hPtr, 3892 TTraits::TypeList<RDFDetail::RInferredType>) 3893 -> decltype(hPtr->Exec(0u), RResultPtr<ActionResultType>{}) 3894 { 3895 return CreateAction<RDFInternal::ActionTags::Book>(/*columns=*/{}, resPtr, hPtr, fProxiedPtr, 0u); 3896 } 3897 3898 template <typename Helper, typename ActionResultType, typename... Others> 3899 RResultPtr<ActionResultType> 3900 CallCreateActionWithoutColsIfPossible(const std::shared_ptr<ActionResultType> &, 3901 const std::shared_ptr<Helper>& /*hPtr*/, 3902 Others...) 3903 { 3904 throw std::logic_error(std::string("An action was booked with no input columns, but the action requires " 3905 "columns! The action helper type was ") + 3906 typeid(Helper).name()); 3907 return {}; 3908 } 3909 3910 protected: 3911 RInterface(const std::shared_ptr<Proxied> &proxied, RLoopManager &lm, 3912 const RDFInternal::RColumnRegister &colRegister) 3913 : RInterfaceBase(lm, colRegister), fProxiedPtr(proxied) 3914 { 3915 } 3916 3917 const std::shared_ptr<Proxied> &GetProxiedPtr() const { return fProxiedPtr; } 3918 }; 3919 3920 } // namespace RDF 3921 3922 } // namespace ROOT 3923 3924 #endif // ROOT_RDF_INTERFACE
| [ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
|
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |
|