diff --git a/core/foundation/inc/ROOT/TypeTraits.hxx b/core/foundation/inc/ROOT/TypeTraits.hxx
index c86e39b9f5ef8aa14427fbe041ebbb9a9d31b474..1abf64e73a89c0e3056d1ba94fd9eb91e81a994e 100644
--- a/core/foundation/inc/ROOT/TypeTraits.hxx
+++ b/core/foundation/inc/ROOT/TypeTraits.hxx
@@ -14,8 +14,6 @@
 
 #include <memory> // shared_ptr, unique_ptr for IsSmartOrDumbPtr
 #include <type_traits>
-#include <vector> // for IsContainer
-#include "ROOT/RSpan.hxx" // for IsContainer
 
 namespace ROOT {
 
@@ -93,42 +91,6 @@ template <class P>
 class IsSmartOrDumbPtr<std::unique_ptr<P>> : public std::true_type {
 };
 
-/// Check for container traits.
-///
-/// Note that this trait selects std::string as container.
-template <typename T>
-struct IsContainer {
-   using Test_t = typename std::decay<T>::type;
-
-   template <typename A>
-   static constexpr bool Test(A *pt, A const *cpt = nullptr, decltype(pt->begin()) * = nullptr,
-                              decltype(pt->end()) * = nullptr, decltype(cpt->begin()) * = nullptr,
-                              decltype(cpt->end()) * = nullptr, typename A::iterator *pi = nullptr,
-                              typename A::const_iterator *pci = nullptr)
-   {
-      using It_t = typename A::iterator;
-      using CIt_t = typename A::const_iterator;
-      using V_t = typename A::value_type;
-      return std::is_same<Test_t, std::vector<bool>>::value ||
-             (std::is_same<decltype(pt->begin()), It_t>::value && std::is_same<decltype(pt->end()), It_t>::value &&
-              std::is_same<decltype(cpt->begin()), CIt_t>::value && std::is_same<decltype(cpt->end()), CIt_t>::value &&
-              std::is_same<decltype(**pi), V_t &>::value && std::is_same<decltype(**pci), V_t const &>::value);
-   }
-
-   template <typename A>
-   static constexpr bool Test(...)
-   {
-      return false;
-   }
-
-   static constexpr bool value = Test<Test_t>(nullptr);
-};
-
-template<typename T>
-struct IsContainer<std::span<T>> {
-   static constexpr bool value = true;
-};
-
 /// Checks for signed integers types that are not characters
 template<class T>
 struct IsSignedNumeral : std::integral_constant<bool,
diff --git a/core/foundation/test/testTypeTraits.cxx b/core/foundation/test/testTypeTraits.cxx
index f9a9b55bce26385fb96dfb213da03eba5885e068..ef00c68f5df2c629475a2257b28a37d83575a6e0 100644
--- a/core/foundation/test/testTypeTraits.cxx
+++ b/core/foundation/test/testTypeTraits.cxx
@@ -41,14 +41,6 @@ TEST(TypeTraits, RemoveFirstParameter)
    ::testing::StaticAssertTypeEq<RemoveFirstParameter_t<std::tuple<void>>, std::tuple<>>();
 }
 
-TEST(TypeTraits, IsContainer)
-{
-   static_assert(IsContainer<std::vector<int>>::value, "");
-   static_assert(IsContainer<std::vector<bool>>::value, "");
-   static_assert(IsContainer<std::tuple<int, int>>::value == false, "");
-   static_assert(IsContainer<std::string>::value, "");
-}
-
 /******** helper objects ***********/
 struct Dummy {
 };
diff --git a/tree/dataframe/inc/ROOT/RDF/ActionHelpers.hxx b/tree/dataframe/inc/ROOT/RDF/ActionHelpers.hxx
index 297abddf9ae449846d27e1aca2b14f0c0fdae501..c52178a365ce124a02254df461cbfac1aaaf225f 100644
--- a/tree/dataframe/inc/ROOT/RDF/ActionHelpers.hxx
+++ b/tree/dataframe/inc/ROOT/RDF/ActionHelpers.hxx
@@ -182,7 +182,7 @@ public:
    void Exec(unsigned int slot, double v);
    void Exec(unsigned int slot, double v, double w);
 
-   template <typename T, typename std::enable_if<IsContainer<T>::value, int>::type = 0>
+   template <typename T, typename std::enable_if<IsDataContainer<T>::value || std::is_same<T, std::string>::value, int>::type = 0>
    void Exec(unsigned int slot, const T &vs)
    {
       auto &thisBuf = fBuffers[slot];
@@ -193,7 +193,7 @@ public:
    }
 
    template <typename T, typename W,
-             typename std::enable_if<IsContainer<T>::value && IsContainer<W>::value, int>::type = 0>
+             typename std::enable_if<IsDataContainer<T>::value && IsDataContainer<W>::value, int>::type = 0>
    void Exec(unsigned int slot, const T &vs, const W &ws)
    {
       auto &thisBuf = fBuffers[slot];
@@ -210,7 +210,7 @@ public:
    }
 
    template <typename T, typename W,
-             typename std::enable_if<IsContainer<T>::value && !IsContainer<W>::value, int>::type = 0>
+             typename std::enable_if<IsDataContainer<T>::value && !IsDataContainer<W>::value, int>::type = 0>
    void Exec(unsigned int slot, const T &vs, const W w)
    {
       auto &thisBuf = fBuffers[slot];
@@ -225,7 +225,7 @@ public:
 
    // ROOT-10092: Filling with a scalar as first column and a collection as second is not supported
    template <typename T, typename W,
-             typename std::enable_if<IsContainer<W>::value && !IsContainer<T>::value, int>::type = 0>
+             typename std::enable_if<IsDataContainer<W>::value && !IsDataContainer<T>::value, int>::type = 0>
    void Exec(unsigned int, const T &, const W &)
    {
       throw std::runtime_error(
@@ -295,7 +295,7 @@ public:
       fObjects[slot]->Fill(x0, x1, x2, x3);
    }
 
-   template <typename X0, typename std::enable_if<IsContainer<X0>::value, int>::type = 0>
+   template <typename X0, typename std::enable_if<IsDataContainer<X0>::value || std::is_same<X0, std::string>::value, int>::type = 0>
    void Exec(unsigned int slot, const X0 &x0s)
    {
       auto thisSlotH = fObjects[slot];
@@ -306,7 +306,7 @@ public:
 
    // ROOT-10092: Filling with a scalar as first column and a collection as second is not supported
    template <typename X0, typename X1,
-             typename std::enable_if<IsContainer<X1>::value && !IsContainer<X0>::value, int>::type = 0>
+             typename std::enable_if<IsDataContainer<X1>::value && !IsDataContainer<X0>::value, int>::type = 0>
    void Exec(unsigned int , const X0 &, const X1 &)
    {
       throw std::runtime_error(
@@ -314,7 +314,7 @@ public:
    }
 
    template <typename X0, typename X1,
-             typename std::enable_if<IsContainer<X0>::value && IsContainer<X1>::value, int>::type = 0>
+             typename std::enable_if<IsDataContainer<X0>::value && IsDataContainer<X1>::value, int>::type = 0>
    void Exec(unsigned int slot, const X0 &x0s, const X1 &x1s)
    {
       auto thisSlotH = fObjects[slot];
@@ -330,7 +330,7 @@ public:
    }
 
    template <typename X0, typename W,
-             typename std::enable_if<IsContainer<X0>::value && !IsContainer<W>::value, int>::type = 0>
+             typename std::enable_if<IsDataContainer<X0>::value && !IsDataContainer<W>::value, int>::type = 0>
    void Exec(unsigned int slot, const X0 &x0s, const W w)
    {
       auto thisSlotH = fObjects[slot];
@@ -340,7 +340,7 @@ public:
    }
 
    template <typename X0, typename X1, typename X2,
-             typename std::enable_if<IsContainer<X0>::value && IsContainer<X1>::value && IsContainer<X2>::value,
+             typename std::enable_if<IsDataContainer<X0>::value && IsDataContainer<X1>::value && IsDataContainer<X2>::value,
                                      int>::type = 0>
    void Exec(unsigned int slot, const X0 &x0s, const X1 &x1s, const X2 &x2s)
    {
@@ -358,7 +358,7 @@ public:
    }
 
    template <typename X0, typename X1, typename W,
-             typename std::enable_if<IsContainer<X0>::value && IsContainer<X1>::value && !IsContainer<W>::value,
+             typename std::enable_if<IsDataContainer<X0>::value && IsDataContainer<X1>::value && !IsDataContainer<W>::value,
                                      int>::type = 0>
    void Exec(unsigned int slot, const X0 &x0s, const X1 &x1s, const W w)
    {
@@ -375,8 +375,8 @@ public:
    }
 
    template <typename X0, typename X1, typename X2, typename X3,
-             typename std::enable_if<IsContainer<X0>::value && IsContainer<X1>::value && IsContainer<X2>::value &&
-                                        IsContainer<X3>::value,
+             typename std::enable_if<IsDataContainer<X0>::value && IsDataContainer<X1>::value && IsDataContainer<X2>::value &&
+                                        IsDataContainer<X3>::value,
                                      int>::type = 0>
    void Exec(unsigned int slot, const X0 &x0s, const X1 &x1s, const X2 &x2s, const X3 &x3s)
    {
@@ -395,8 +395,8 @@ public:
    }
 
    template <typename X0, typename X1, typename X2, typename W,
-             typename std::enable_if<IsContainer<X0>::value && IsContainer<X1>::value && IsContainer<X2>::value &&
-                                        !IsContainer<W>::value,
+             typename std::enable_if<IsDataContainer<X0>::value && IsDataContainer<X1>::value && IsDataContainer<X2>::value &&
+                                        !IsDataContainer<W>::value,
                                      int>::type = 0>
    void Exec(unsigned int slot, const X0 &x0s, const X1 &x1s, const X2 &x2s, const W w)
    {
@@ -459,8 +459,7 @@ public:
    void InitTask(TTreeReader *, unsigned int) {}
 
    template <typename X0, typename X1,
-             typename std::enable_if<
-                ROOT::TypeTraits::IsContainer<X0>::value && ROOT::TypeTraits::IsContainer<X1>::value, int>::type = 0>
+             typename std::enable_if<IsDataContainer<X0>::value && IsDataContainer<X1>::value, int>::type = 0>
    void Exec(unsigned int slot, const X0 &x0s, const X1 &x1s)
    {
       if (x0s.size() != x1s.size()) {
@@ -727,7 +726,7 @@ public:
 
    void InitTask(TTreeReader *, unsigned int) {}
 
-   template <typename T, typename std::enable_if<IsContainer<T>::value, int>::type = 0>
+   template <typename T, typename std::enable_if<IsDataContainer<T>::value, int>::type = 0>
    void Exec(unsigned int slot, const T &vs)
    {
       for (auto &&v : vs)
@@ -771,7 +770,7 @@ public:
    void InitTask(TTreeReader *, unsigned int) {}
    void Exec(unsigned int slot, ResultType v) { fMaxs[slot] = std::max(v, fMaxs[slot]); }
 
-   template <typename T, typename std::enable_if<IsContainer<T>::value, int>::type = 0>
+   template <typename T, typename std::enable_if<IsDataContainer<T>::value, int>::type = 0>
    void Exec(unsigned int slot, const T &vs)
    {
       for (auto &&v : vs)
@@ -831,7 +830,7 @@ public:
    void InitTask(TTreeReader *, unsigned int) {}
    void Exec(unsigned int slot, ResultType v) { fSums[slot] += v; }
 
-   template <typename T, typename std::enable_if<IsContainer<T>::value, int>::type = 0>
+   template <typename T, typename std::enable_if<IsDataContainer<T>::value, int>::type = 0>
    void Exec(unsigned int slot, const T &vs)
    {
       for (auto &&v : vs)
@@ -864,7 +863,7 @@ public:
    void InitTask(TTreeReader *, unsigned int) {}
    void Exec(unsigned int slot, double v);
 
-   template <typename T, typename std::enable_if<IsContainer<T>::value, int>::type = 0>
+   template <typename T, typename std::enable_if<IsDataContainer<T>::value, int>::type = 0>
    void Exec(unsigned int slot, const T &vs)
    {
       for (auto &&v : vs) {
@@ -906,7 +905,7 @@ public:
    void InitTask(TTreeReader *, unsigned int) {}
    void Exec(unsigned int slot, double v);
 
-   template <typename T, typename std::enable_if<IsContainer<T>::value, int>::type = 0>
+   template <typename T, typename std::enable_if<IsDataContainer<T>::value, int>::type = 0>
    void Exec(unsigned int slot, const T &vs)
    {
       for (auto &&v : vs) {
diff --git a/tree/dataframe/inc/ROOT/RDF/InterfaceUtils.hxx b/tree/dataframe/inc/ROOT/RDF/InterfaceUtils.hxx
index 06d0e355ed11c840332a724f4542ce8c3c04ffe8..47a3fd6458b2758f3c186ba8a717d82998230210 100644
--- a/tree/dataframe/inc/ROOT/RDF/InterfaceUtils.hxx
+++ b/tree/dataframe/inc/ROOT/RDF/InterfaceUtils.hxx
@@ -408,7 +408,7 @@ void CallBuildAction(std::shared_ptr<PrevNodeType> *prevNodeOnHeap, const Column
 }
 
 /// The contained `type` alias is `double` if `T == RInferredType`, `U` if `T == std::container<U>`, `T` otherwise.
-template <typename T, bool Container = TTraits::IsContainer<T>::value && !std::is_same<T, std::string>::value>
+template <typename T, bool Container = RDFInternal::IsDataContainer<T>::value && !std::is_same<T, std::string>::value>
 struct TMinReturnType {
    using type = T;
 };
diff --git a/tree/dataframe/inc/ROOT/RDF/RDisplay.hxx b/tree/dataframe/inc/ROOT/RDF/RDisplay.hxx
index c88e8ae78c0d791a4fd35d10a6373cc593670488..90058f070660da51b74cd75e3ce3dfbed86458d3 100644
--- a/tree/dataframe/inc/ROOT/RDF/RDisplay.hxx
+++ b/tree/dataframe/inc/ROOT/RDF/RDisplay.hxx
@@ -96,7 +96,7 @@ private:
    /// \param[in] element The event to convert to its string representation
    /// \param[in] index To which column the event belongs to
    /// \return false, the event is not a collection
-   template <typename T, typename std::enable_if<std::is_same<T, std::string>::value || !ROOT::TypeTraits::IsContainer<T>::value, int>::type = 0>
+   template <typename T, typename std::enable_if<!ROOT::Internal::RDF::IsDataContainer<T>::value, int>::type = 0>
    bool AddInterpreterString(std::stringstream &stream, T &element, const int &index)
    {
       stream << "*((std::string*)" << ROOT::Internal::RDF::PrettyPrintAddr(&(fRepresentations[index]))
@@ -112,7 +112,7 @@ private:
    /// \param[in] index To which column the event belongs to
    /// \return true, the event is a collection
    /// This function chains a sequence of call to cling::printValue, one for each element of the collection.
-   template <typename T, typename std::enable_if<!std::is_same<T, std::string>::value && ROOT::TypeTraits::IsContainer<T>::value, int>::type = 0>
+   template <typename T, typename std::enable_if<ROOT::Internal::RDF::IsDataContainer<T>::value, int>::type = 0>
    bool AddInterpreterString(std::stringstream &stream, T &collection, const int &index)
    {
       size_t collectionSize = std::distance(std::begin(collection), std::end(collection));
diff --git a/tree/dataframe/inc/ROOT/RDF/Utils.hxx b/tree/dataframe/inc/ROOT/RDF/Utils.hxx
index fcd36bda30128e9747ba8c8b92020192f4fa9035..9fbe5eee5d7b6c9cf3b056b0cc1ab36bc4f5b041 100644
--- a/tree/dataframe/inc/ROOT/RDF/Utils.hxx
+++ b/tree/dataframe/inc/ROOT/RDF/Utils.hxx
@@ -15,6 +15,7 @@
 #include "ROOT/TypeTraits.hxx"
 #include "ROOT/RVec.hxx"
 #include "ROOT/RSnapshotOptions.hxx"
+#include "ROOT/RSpan.hxx" // for IsDataContainer
 #include "TH1.h"
 
 #include <array>
@@ -23,7 +24,6 @@
 #include <memory>
 #include <string>
 #include <type_traits> // std::decay
-#include <vector>
 
 class TTree;
 class TTreeReader;
@@ -52,6 +52,43 @@ using namespace ROOT::TypeTraits;
 using namespace ROOT::Detail::RDF;
 using namespace ROOT::RDF;
 
+/// Check for container traits.
+///
+/// Note that we don't recognize std::string as a container.
+template <typename T>
+struct IsDataContainer {
+   using Test_t = typename std::decay<T>::type;
+
+   template <typename A>
+   static constexpr bool Test(A *pt, A const *cpt = nullptr, decltype(pt->begin()) * = nullptr,
+                              decltype(pt->end()) * = nullptr, decltype(cpt->begin()) * = nullptr,
+                              decltype(cpt->end()) * = nullptr, typename A::iterator *pi = nullptr,
+                              typename A::const_iterator *pci = nullptr)
+   {
+      using It_t = typename A::iterator;
+      using CIt_t = typename A::const_iterator;
+      using V_t = typename A::value_type;
+      return std::is_same<Test_t, std::vector<bool>>::value ||
+             (std::is_same<decltype(pt->begin()), It_t>::value && std::is_same<decltype(pt->end()), It_t>::value &&
+              std::is_same<decltype(cpt->begin()), CIt_t>::value && std::is_same<decltype(cpt->end()), CIt_t>::value &&
+              std::is_same<decltype(**pi), V_t &>::value && std::is_same<decltype(**pci), V_t const &>::value &&
+              !std::is_same<T, std::string>::value);
+   }
+
+   template <typename A>
+   static constexpr bool Test(...)
+   {
+      return false;
+   }
+
+   static constexpr bool value = Test<Test_t>(nullptr);
+};
+
+template<typename T>
+struct IsDataContainer<std::span<T>> {
+   static constexpr bool value = true;
+};
+
 /// Detect whether a type is an instantiation of vector<T,A>
 template <typename>
 struct IsVector_t : public std::false_type {};
@@ -107,7 +144,7 @@ struct IsRVec_t<ROOT::VecOps::RVec<T>> : public std::true_type {};
 
 // Check the value_type type of a type with a SFINAE to allow compilation in presence
 // fundamental types
-template <typename T, bool IsContainer = IsContainer<typename std::decay<T>::type>::value>
+template <typename T, bool IsDataContainer = IsDataContainer<typename std::decay<T>::type>::value || std::is_same<std::string, T>::value>
 struct ValueType {
    using value_type = typename T::value_type;
 };
diff --git a/tree/dataframe/test/dataframe_utils.cxx b/tree/dataframe/test/dataframe_utils.cxx
index 77477d10b0c8d1aa3a1ba88629f440ecdd1fc362..4e64efe5e803e94ca44a12e355c45bfc1c07c08e 100644
--- a/tree/dataframe/test/dataframe_utils.cxx
+++ b/tree/dataframe/test/dataframe_utils.cxx
@@ -240,3 +240,11 @@ TEST(RDataFrameUtils, FindUnknownColumnsFriendTrees)
    auto ncols = RDFInt::FindUnknownColumns({"c2", "c3", "c4"}, RDFInt::GetBranchNames(t1), {}, {});
    EXPECT_EQ(ncols.size(), 0u) << "Cannot find column in friend trees.";
 }
+
+TEST(RDataFrameUtils, IsDataContainer)
+{
+   static_assert(RDFInt::IsDataContainer<std::vector<int>>::value, "");
+   static_assert(RDFInt::IsDataContainer<std::vector<bool>>::value, "");
+   static_assert(RDFInt::IsDataContainer<std::tuple<int, int>>::value == false, "");
+   static_assert(RDFInt::IsDataContainer<std::string>::value == false, "");
+}
diff --git a/tutorials/dataframe/df022_useKahan.C b/tutorials/dataframe/df022_useKahan.C
index f0c4a2e8478116bbaee4a47449389e092e884427..577e9dfe23040b8624cbae606a97d9a854e86b49 100644
--- a/tutorials/dataframe/df022_useKahan.C
+++ b/tutorials/dataframe/df022_useKahan.C
@@ -52,7 +52,7 @@ public:
       KahanAlgorithm(x, fPartialSums[slot], fCompensations[slot]);
    }
 
-   template <typename V=T, typename std::enable_if<ROOT::TypeTraits::IsContainer<V>::value, int>::type = 0>
+   template <typename V=T, typename std::enable_if<ROOT::Internal::RDF::IsDataContainer<V>::value, int>::type = 0>
    void Exec(unsigned int slot, const T &vs)
    {
       for (auto &&v : vs) {