Skip to content

Commit 068ce3d

Browse files
MehkaanKhanvepadulano
authored andcommitted
[RDF] Add BarChart() action for categorical/string columns
Histo1D only supports numeric columns; there was no way to fill a histogram from a string/categorical column (e.g. particle type, detector region labels) without manual workarounds. This adds a BarChart() lazy action that fills a label-binned, auto-extending TH1D from either std::string columns or classic C-style char[] tree branches (inferred as RVec<char> by RDataFrame). - ActionTags::BarChart + a dedicated BuildAction overload - BarChartHelper, since FillHelper<TH1D> cannot compile against either std::string or RVec<char> (no implicit conversion to the const char* Fill overload) - RInterface::BarChart(vName, name, title) - Test coverage in dataframe_histomodels.cxx (bin ordering/counts) and dataframe_simple.cxx (auto name/title derivation) Closes #17057
1 parent 787952f commit 068ce3d

5 files changed

Lines changed: 132 additions & 0 deletions

File tree

tree/dataframe/inc/ROOT/RDF/ActionHelpers.hxx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,66 @@ public:
483483
}
484484
};
485485

486+
// BarChartHelper: fills a TH1D from string/categorical columns via TH1D::Fill(const char*).
487+
// FillHelper<TH1D>::Exec cannot do this: std::string has no implicit conversion to const char*,
488+
// so its non-container Exec overload's decltype(Fill(x...)) SFINAEs out, and std::string is
489+
// explicitly excluded from IsDataContainer (see Utils.hxx), so the container-fill overload
490+
// SFINAEs out too -- FillHelper<TH1D> simply won't compile for a std::string column.
491+
class R__CLING_PTRCHECK(off) BarChartHelper : public RActionImpl<BarChartHelper> {
492+
std::vector<::TH1D *> fObjects;
493+
494+
public:
495+
BarChartHelper(BarChartHelper &&) = default;
496+
BarChartHelper(const BarChartHelper &) = delete;
497+
498+
BarChartHelper(const std::shared_ptr<::TH1D> &h, const unsigned int nSlots) : fObjects(nSlots, nullptr)
499+
{
500+
fObjects[0] = h.get();
501+
for (unsigned int i = 1; i < nSlots; ++i) {
502+
fObjects[i] = new ::TH1D(*fObjects[0]);
503+
UnsetDirectoryIfPossible(fObjects[i]);
504+
}
505+
}
506+
507+
void InitTask(TTreeReader *, unsigned int) {}
508+
509+
void Exec(unsigned int slot, const std::string &label) { fObjects[slot]->Fill(label.c_str(), 1.0); }
510+
511+
void Exec(unsigned int slot, const ROOT::VecOps::RVec<char> &label)
512+
{
513+
fObjects[slot]->Fill(std::string(label.begin(), label.end()).c_str(), 1.0);
514+
}
515+
516+
void Initialize() { /* noop */ }
517+
518+
void Finalize()
519+
{
520+
if (fObjects.size() > 1) {
521+
TList l;
522+
for (auto it = ++fObjects.begin(); it != fObjects.end(); ++it)
523+
l.Add(*it);
524+
fObjects[0]->Merge(&l);
525+
526+
for (auto it = ++fObjects.begin(); it != fObjects.end(); ++it)
527+
delete *it;
528+
}
529+
fObjects[0]->LabelsDeflate("X");
530+
}
531+
532+
::TH1D &PartialUpdate(unsigned int slot) { return *fObjects[slot]; }
533+
534+
std::string GetActionName() { return std::string("BarChart\n") + fObjects[0]->GetName(); }
535+
536+
BarChartHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
537+
{
538+
auto &result = *static_cast<std::shared_ptr<::TH1D> *>(newResult);
539+
ResetIfPossible(result.get());
540+
UnsetDirectoryIfPossible(result.get());
541+
return BarChartHelper(result, fObjects.size());
542+
}
543+
};
544+
545+
486546
#ifdef R__HAS_ROOT7
487547
template <typename BinContentType, bool WithWeight = false>
488548
class R__CLING_PTRCHECK(off) RHistFillHelper

tree/dataframe/inc/ROOT/RDF/InterfaceUtils.hxx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public:
8787
/// This namespace defines types to be used for tag dispatching in RInterface.
8888
namespace ActionTags {
8989
struct Histo1D{};
90+
struct BarChart{};
9091
struct Histo2D{};
9192
struct Histo3D{};
9293
struct HistoND{};
@@ -153,6 +154,18 @@ BuildAction(const ColumnNames_t &bl, const std::shared_ptr<::TH1D> &h, const uns
153154
}
154155
}
155156

157+
// BarChart filling: always uses BarChartHelper (labels have no numeric axis limits to check)
158+
template <typename... ColTypes, typename PrevNodeType>
159+
std::unique_ptr<RActionBase>
160+
BuildAction(const ColumnNames_t &bl, const std::shared_ptr<::TH1D> &h, const unsigned int nSlots,
161+
std::shared_ptr<PrevNodeType> prevNode, ActionTags::BarChart, const RColumnRegister &colRegister)
162+
{
163+
using Helper_t = BarChartHelper;
164+
using Action_t = RAction<Helper_t, PrevNodeType, TTraits::TypeList<ColTypes...>>;
165+
return std::make_unique<Action_t>(Helper_t(h, nSlots), bl, std::move(prevNode), colRegister);
166+
}
167+
168+
156169
// Action for Histo3D, where thread safe filling might be supported to save memory
157170
template <typename... ColTypes, typename ActionResultType, typename PrevNodeType>
158171
std::unique_ptr<RActionBase>

tree/dataframe/inc/ROOT/RDF/RInterface.hxx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,35 @@ public:
14991499
return Histo1D<V>({h_name.c_str(), h_title.c_str(), 128u, 0., 0.}, vName);
15001500
}
15011501

1502+
////////////////////////////////////////////////////////////////////////////
1503+
/// \brief Fill and return a one-dimensional histogram with the values of a categorical/string column (*lazy action*).
1504+
/// \tparam V The type of the column used to fill the histogram (must be std::string).
1505+
/// \param[in] vName The name of the column that will fill the histogram.
1506+
/// \param[in] name The name of the returned histogram. Defaults to the column name.
1507+
/// \param[in] title The title of the returned histogram. Defaults to "<name>;<name>;count".
1508+
/// \return the bar chart histogram wrapped in a RResultPtr, one bin per distinct label encountered.
1509+
///
1510+
/// The returned TH1D has an extendable axis (TH1::kAllAxes) that grows a new bin for each
1511+
/// previously-unseen label; bins are ordered by first-encounter order, not sorted.
1512+
///
1513+
/// ### Example usage:
1514+
/// ~~~{.cpp}
1515+
/// auto h = myDf.BarChart("Nation");
1516+
/// ~~~
1517+
template <typename V = RDFDetail::RInferredType>
1518+
RResultPtr<::TH1D> BarChart(std::string_view vName, std::string_view name = "", std::string_view title = "")
1519+
{
1520+
const auto h_name = name.empty() ? std::string(vName) : std::string(name);
1521+
const auto h_title = title.empty() ? (h_name + ";" + h_name + ";count") : std::string(title);
1522+
const auto userColumns = ColumnNames_t({std::string(vName)});
1523+
const auto validatedColumns = GetValidatedColumnNames(1, userColumns);
1524+
1525+
std::shared_ptr<::TH1D> h(new ::TH1D(h_name.c_str(), h_title.c_str(), 1, 0., 1.));
1526+
h->SetCanExtend(::TH1::kAllAxes);
1527+
return CreateAction<RDFInternal::ActionTags::BarChart, V>(validatedColumns, h, h, fProxiedPtr);
1528+
}
1529+
1530+
15021531
////////////////////////////////////////////////////////////////////////////
15031532
/// \brief Fill and return a one-dimensional histogram with the weighted values of a column (*lazy action*).
15041533
/// \tparam V The type of the column used to fill the histogram.

tree/dataframe/test/dataframe_histomodels.cxx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,24 @@ TEST(RDataFrameHistoModels, Histo1D)
6262
CheckBins(h2edgesd->GetXaxis(), edgesd);
6363
}
6464

65+
TEST(RDataFrameHistoModels, BarChart)
66+
{
67+
ROOT::RDataFrame tdf(6);
68+
std::vector<std::string> labels{"a", "b", "a", "c", "b", "a"};
69+
auto i = 0u;
70+
auto d = tdf.Define("x", [&i, &labels]() { return labels[i++]; });
71+
auto h = d.BarChart("x");
72+
73+
ASSERT_EQ(h->GetNbinsX(), 3);
74+
EXPECT_STREQ(h->GetXaxis()->GetBinLabel(1), "a");
75+
EXPECT_STREQ(h->GetXaxis()->GetBinLabel(2), "b");
76+
EXPECT_STREQ(h->GetXaxis()->GetBinLabel(3), "c");
77+
EXPECT_DOUBLE_EQ(h->GetBinContent(1), 3);
78+
EXPECT_DOUBLE_EQ(h->GetBinContent(2), 2);
79+
EXPECT_DOUBLE_EQ(h->GetBinContent(3), 1);
80+
}
81+
82+
6583
TEST(RDataFrameHistoModels, Prof1D)
6684
{
6785
ROOT::RDataFrame tdf(10);

tree/dataframe/test/dataframe_simple.cxx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,18 @@ TEST(RDFSimpleTests, AutomaticNamesOfHisto1DAndGraph)
824824
EXPECT_STREQ(gxy->GetYaxis()->GetTitle(), "y");
825825
}
826826

827+
TEST(RDFSimpleTests, AutomaticNamesOfBarChart)
828+
{
829+
auto df = RDataFrame(1).Define("x", []() { return std::string("a"); });
830+
auto hx = df.BarChart("x");
831+
832+
EXPECT_STREQ(hx->GetName(), "x");
833+
EXPECT_STREQ(hx->GetTitle(), "x");
834+
EXPECT_STREQ(hx->GetXaxis()->GetTitle(), "x");
835+
EXPECT_STREQ(hx->GetYaxis()->GetTitle(), "count");
836+
}
837+
838+
827839
TEST_P(RDFSimpleTests, DifferentTreesInDifferentThreads)
828840
{
829841
const auto filename = "DifferentTreesInDifferentThreads.root";

0 commit comments

Comments
 (0)