diff --git a/hist/hist/inc/TProfile.h b/hist/hist/inc/TProfile.h index 2149b1b478aea..e1b00a80bd4b2 100644 --- a/hist/hist/inc/TProfile.h +++ b/hist/hist/inc/TProfile.h @@ -89,7 +89,8 @@ class TProfile : public TH1D { Bool_t Add(const TH1 *h1, const TH1 *h2, Double_t c1=1, Double_t c2=1) override; // *MENU* static void Approximate(Bool_t approx=kTRUE); Int_t BufferEmpty(Int_t action=0) override; - void BuildOptions(Double_t ymin, Double_t ymax, Option_t *option); + void BuildOptions(Double_t ymin, Double_t ymax, Option_t *option); + Double_t Chi2Test(const TH1* h2, Option_t *option = "WW", Double_t *res = nullptr) const override; void Copy(TObject &hnew) const override; Bool_t Divide(TF1 *h1, Double_t c1=1) override; Bool_t Divide(const TH1 *h1) override; diff --git a/hist/hist/inc/TProfile2D.h b/hist/hist/inc/TProfile2D.h index f7a0edc755600..564141207c4f3 100644 --- a/hist/hist/inc/TProfile2D.h +++ b/hist/hist/inc/TProfile2D.h @@ -95,6 +95,7 @@ class TProfile2D : public TH2D { static void Approximate(Bool_t approx=kTRUE); void BuildOptions(Double_t zmin, Double_t zmax, Option_t *option); Int_t BufferEmpty(Int_t action=0) override; + Double_t Chi2Test(const TH1* h2, Option_t *option = "WW", Double_t *res = nullptr) const override; void Copy(TObject &hnew) const override; Bool_t Divide(TF1 *h1, Double_t c1=1) override; Bool_t Divide(const TH1 *h1) override; diff --git a/hist/hist/inc/TProfile3D.h b/hist/hist/inc/TProfile3D.h index 4fe600436ff10..93ce0f310e793 100644 --- a/hist/hist/inc/TProfile3D.h +++ b/hist/hist/inc/TProfile3D.h @@ -100,6 +100,7 @@ class TProfile3D : public TH3D { static void Approximate(Bool_t approx=kTRUE); void BuildOptions(Double_t tmin, Double_t tmax, Option_t *option); Int_t BufferEmpty(Int_t action=0) override; + Double_t Chi2Test(const TH1* h2, Option_t *option = "WW", Double_t *res = nullptr) const override; void Copy(TObject &hnew) const override; Bool_t Divide(TF1 *h1, Double_t c1=1) override; Bool_t Divide(const TH1 *h1) override; diff --git a/hist/hist/src/TProfile.cxx b/hist/hist/src/TProfile.cxx index b12163e53c3b5..355baa9df9f82 100644 --- a/hist/hist/src/TProfile.cxx +++ b/hist/hist/src/TProfile.cxx @@ -416,6 +416,34 @@ Int_t TProfile::BufferFill(Double_t x, Double_t y, Double_t w) return -2; } +//////////////////////////////////////////////////////////////////////////////// +/// Run a Chi2Test between a TProfileD and another histogram. +/// If the argument is also a TProfileD, this calls TH1::Chi2Test() with the option "WW". +/// \see TH1::Chi2Test() + +Double_t TProfile::Chi2Test(const TH1 *h2, Option_t *option, Double_t *res) const +{ + TString opt = option; + opt.ToUpper(); + + if (auto other = dynamic_cast(h2); other) { + if (fErrorMode != kERRORMEAN || other->fErrorMode != kERRORMEAN) { + Error("Chi2Test", "Chi2 tests need TProfiles in 'error of mean' mode."); + return 0; + } + + opt += "WW"; + opt.ReplaceAll("UU", "WW"); + opt.ReplaceAll("UW", "WW"); + } else if (!opt.Contains("WW")) { + Error("Chi2Test", "TProfiles need to be tested with the 'W' option. Either use option 'WW' or use " + "histogram.Chi2Test(, 'UW')"); + return 0; + } + + return TH1::Chi2Test(h2, opt, res); +} + //////////////////////////////////////////////////////////////////////////////// /// Copy a Profile histogram to a new profile histogram. diff --git a/hist/hist/src/TProfile2D.cxx b/hist/hist/src/TProfile2D.cxx index dae1db38f7baa..d15fd3f169bce 100644 --- a/hist/hist/src/TProfile2D.cxx +++ b/hist/hist/src/TProfile2D.cxx @@ -379,6 +379,34 @@ Int_t TProfile2D::BufferFill(Double_t x, Double_t y, Double_t z, Double_t w) return -2; } +//////////////////////////////////////////////////////////////////////////////// +/// Run a Chi2Test between a TProfile2D and another histogram. +/// If the argument is also a TProfile2D, this calls TH1::Chi2Test() with the option "WW". +/// \see TH1::Chi2Test() + +Double_t TProfile2D::Chi2Test(const TH1 *h2, Option_t *option, Double_t *res) const +{ + TString opt = option; + opt.ToUpper(); + + if (auto other = dynamic_cast(h2); other) { + if (fErrorMode != kERRORMEAN || other->fErrorMode != kERRORMEAN) { + Error("Chi2Test", "Chi2 tests need TProfiles in 'error of mean' mode."); + return 0; + } + + opt += "WW"; + opt.ReplaceAll("UU", "WW"); + opt.ReplaceAll("UW", "WW"); + } else if (!opt.Contains("WW")) { + Error("Chi2Test", "TProfiles need to be tested with the 'W' option. Either use option 'WW' or use " + "histogram.Chi2Test(, 'UW')"); + return 0; + } + + return TH1::Chi2Test(h2, opt, res); +} + //////////////////////////////////////////////////////////////////////////////// /// Copy a Profile2D histogram to a new profile2D histogram. diff --git a/hist/hist/src/TProfile3D.cxx b/hist/hist/src/TProfile3D.cxx index bc3fde80da17c..252f1b1e49da7 100644 --- a/hist/hist/src/TProfile3D.cxx +++ b/hist/hist/src/TProfile3D.cxx @@ -343,6 +343,34 @@ Int_t TProfile3D::BufferFill(Double_t x, Double_t y, Double_t z, Double_t t, Dou return -2; } +//////////////////////////////////////////////////////////////////////////////// +/// Run a Chi2Test between a TProfile3D and another histogram. +/// If the argument is also a TProfile3D, this calls TH1::Chi2Test() with the option "WW". +/// \see TH1::Chi2Test() + +Double_t TProfile3D::Chi2Test(const TH1 *h2, Option_t *option, Double_t *res) const +{ + TString opt = option; + opt.ToUpper(); + + if (auto other = dynamic_cast(h2); other) { + if (fErrorMode != kERRORMEAN || other->fErrorMode != kERRORMEAN) { + Error("Chi2Test", "Chi2 tests need TProfiles in 'error of mean' mode."); + return 0; + } + + opt += "WW"; + opt.ReplaceAll("UU", "WW"); + opt.ReplaceAll("UW", "WW"); + } else if (!opt.Contains("WW")) { + Error("Chi2Test", "TProfiles need to be tested with the 'W' option. Either use option 'WW' or use " + "histogram.Chi2Test(, 'UW')"); + return 0; + } + + return TH1::Chi2Test(h2, opt, res); +} + //////////////////////////////////////////////////////////////////////////////// /// Copy a Profile3D histogram to a new profile2D histogram. diff --git a/hist/hist/test/CMakeLists.txt b/hist/hist/test/CMakeLists.txt index 9e5838e616ad8..b22647a8c6088 100644 --- a/hist/hist/test/CMakeLists.txt +++ b/hist/hist/test/CMakeLists.txt @@ -29,6 +29,7 @@ ROOT_ADD_GTEST(testTGraphSorting test_TGraph_sorting.cxx LIBRARIES Hist) ROOT_ADD_GTEST(testSpline test_spline.cxx LIBRARIES Hist) ROOT_ADD_GTEST(testTF1Simple test_tf1_simple.cxx LIBRARIES Hist RIO) ROOT_ADD_GTEST(testTF1DrawCopy test_tf1_drawcopy.cxx LIBRARIES Hist) +ROOT_ADD_GTEST(test_TProfile test_TProfile.cxx LIBRARIES Hist) if(fftw3) ROOT_ADD_GTEST(testTF1 test_tf1.cxx LIBRARIES Hist) diff --git a/hist/hist/test/test_TProfile.cxx b/hist/hist/test/test_TProfile.cxx new file mode 100644 index 0000000000000..c08d0d2563608 --- /dev/null +++ b/hist/hist/test/test_TProfile.cxx @@ -0,0 +1,93 @@ +#include "gtest/gtest.h" + +#include "TProfile.h" +#include "TProfile2D.h" +#include "TProfile3D.h" +#include "TRandom.h" + +#include "ROOT/TestSupport.hxx" + +template +void runTest(T const &reference, T const &sameDistr, T const &differentDistr) +{ + EXPECT_EQ(reference.Chi2Test(&sameDistr), reference.Chi2Test(&sameDistr, "WW")); + EXPECT_EQ(reference.Chi2Test(&sameDistr), + reference.Chi2Test(&sameDistr, "P WW UW")); // Need more than just the default option + EXPECT_EQ(reference.Chi2Test(&sameDistr), reference.Chi2Test(&sameDistr, "UW")); + EXPECT_EQ(reference.Chi2Test(&sameDistr), reference.Chi2Test(&sameDistr, "UU")); + EXPECT_EQ(reference.Chi2Test(&sameDistr), reference.Chi2Test(&sameDistr, "P UU")); + + const double probSuccess = reference.Chi2Test(&sameDistr, "P"); + EXPECT_GT(probSuccess, 0.1); + EXPECT_LE(probSuccess, 1.); + const double probFail = reference.Chi2Test(&differentDistr, "P"); + EXPECT_LT(probFail, 0.05); +} + +TEST(TProfile, Chi2Test) +{ + TProfile reference("reference", "reference", 10, 0, 10); + TProfile sameDistr("sameDistr", "sameDistr", 10, 0, 10); + TProfile differentDistr("differentDistr", "differentDistr", 10, 0, 10); + + gRandom->SetSeed(1); + for (unsigned int i = 0; i < 100000; i++) { + const double x = gRandom->Uniform(10.); + reference.Fill(x, gRandom->Gaus(5 + x / 2, 5.)); + sameDistr.Fill(x, gRandom->Gaus(5 + x / 2, 5.)); + differentDistr.Fill(x, gRandom->Gaus(20, 1.)); + } + + runTest(reference, sameDistr, differentDistr); +} + +TEST(TProfile, Chi2TestWithWrongErrors) +{ + TProfile reference("reference", "reference", 10, 0, 10); + reference.Fill(1, 2); + reference.Fill(1, 3); + + for (auto err : {"s", "i", "g"}) { + ROOT::TestSupport::CheckDiagsRAII checkDiag(kError, "TProfile::Chi2Test", "error of mean", false); + + TProfile sameDistr("sameDistr", "sameDistr", 10, 0, 10, err); + sameDistr.Fill(1, 2); + sameDistr.Fill(1, 3); + + reference.Chi2Test(&sameDistr); + } +} + +TEST(TProfile2D, Chi2Test) +{ + TProfile2D reference("reference", "reference", 10, 0, 10, 10, 0, 10); + TProfile2D sameDistr("sameDistr", "sameDistr", 10, 0, 10, 10, 0, 10); + TProfile2D differentDistr("differentDistr", "differentDistr", 10, 0, 10, 10, 0, 10); + + gRandom->SetSeed(1); + for (unsigned int i = 0; i < 50000; i++) { + const double x = gRandom->Uniform(10.); + reference.Fill(x, x + 1., gRandom->Gaus(5 + x / 2, 5.)); + sameDistr.Fill(x, x + 1., gRandom->Gaus(5 + x / 2, 5.)); + differentDistr.Fill(x, x + 1., gRandom->Gaus(20, 1.)); + } + + runTest(reference, sameDistr, differentDistr); +} + +TEST(TProfile3D, Chi2Test) +{ + TProfile3D reference("reference", "reference", 10, 0, 10, 11, 0, 11, 12, 0, 12); + TProfile3D sameDistr("sameDistr", "sameDistr", 10, 0, 10, 11, 0, 11, 12, 0, 12); + TProfile3D differentDistr("differentDistr", "differentDistr", 10, 0, 10, 11, 0, 11, 12, 0, 12); + + gRandom->SetSeed(1); + for (unsigned int i = 0; i < 50000; i++) { + const double x = gRandom->Uniform(10.); + reference.Fill(x, x + 1., x + 2., gRandom->Gaus(5 + x / 2, 5.)); + sameDistr.Fill(x, x + 1., x + 2., gRandom->Gaus(5 + x / 2, 5.)); + differentDistr.Fill(x, x + 1., x + 2., gRandom->Gaus(20, 1.)); + } + + runTest(reference, sameDistr, differentDistr); +} \ No newline at end of file