SeriesGroupBy ============= .. cpp:class:: pandas::SeriesGroupBy GroupBy class for split-apply-combine operations. Example ------- .. code-block:: cpp #include using namespace pandas; // Use SeriesGroupBy SeriesGroupBy obj; // ... operations ... Constructors ------------ .. list-table:: :widths: 55 25 20 :header-rows: 1 * - Signature - Location - Example * - ``SeriesGroupBy(const Series& series, const Series& by, bool sort = true)`` - pd_series_groupby.h:78 - * - ``SeriesGroupBy(std::shared_ptr> owned, const Series& by, bool sort = true)`` - pd_series_groupby.h:89 - Construction ------------ .. list-table:: :widths: 40 20 15 25 :header-rows: 1 * - Signature - Return Type - Location - Example * - ``Series create_result_series(const std::vector& values) const`` - Series - pd_series_groupby.h:1157 - * - ``Series create_result_series_double(const std::vector& values) const`` - Series - pd_series_groupby.h:1174 - * - ``Series create_result_series_int64(const std::vector& values) const`` - Series - pd_series_groupby.h:1193 - Indexing / Selection -------------------- .. list-table:: :widths: 40 20 15 25 :header-rows: 1 * - Signature - Return Type - Location - Example * - ``Series first() const`` - Series - pd_series_groupby.h:380 - :ref:`View ` * - ``Series get_group(const GroupT& key) const`` - Series - pd_series_groupby.h:1063 - :ref:`View ` * - ``std::optional get_index_name() const`` - std::optional - pd_series_groupby.h:128 - :ref:`View ` * - ``cat_values, cats, false, get_index_name())`` - cat_values, cats, false, - pd_series_groupby.h:1108 - :ref:`View ` * - ``std::optional get_series_name() const`` - std::optional - pd_series_groupby.h:131 - * - ``pandas::Result idxmin_with_dtype() const`` - pandas::Result - pd_series_groupby.h:728 - :ref:`View ` * - ``Series last() const`` - Series - pd_series_groupby.h:472 - :ref:`View ` Data Manipulation ----------------- .. list-table:: :widths: 40 20 15 25 :header-rows: 1 * - Signature - Return Type - Location - Example * - ``void set_index_name(const std::string& name)`` - void - pd_series_groupby.h:120 - :ref:`View ` Statistics ---------- .. list-table:: :widths: 40 20 15 25 :header-rows: 1 * - Signature - Return Type - Location - Example * - ``Series count() const`` - Series - pd_series_groupby.h:276 - :ref:`View ` * - ``Series cummax() const`` - Series - pd_series_groupby.h:852 - :ref:`View ` * - ``Series cummin() const`` - Series - pd_series_groupby.h:877 - :ref:`View ` * - ``Series cumprod() const`` - Series - pd_series_groupby.h:827 - :ref:`View ` * - ``Series cumsum() const`` - Series - pd_series_groupby.h:802 - :ref:`View ` * - ``Series max() const`` - Series - pd_series_groupby.h:342 - :ref:`View ` * - ``Series mean() const`` - Series - pd_series_groupby.h:240 - :ref:`View ` * - ``Series median() const`` - Series - pd_series_groupby.h:589 - :ref:`View ` * - ``Series min() const`` - Series - pd_series_groupby.h:304 - :ref:`View ` * - ``Series nunique(bool dropna = true) const`` - Series - pd_series_groupby.h:955 - :ref:`View ` * - ``Series std_(int ddof = 1) const`` - Series - pd_series_groupby.h:510 - :ref:`View ` * - ``auto sum() const`` - auto - pd_series_groupby.h:180 - :ref:`View ` * - ``Series sum_int64_bool_() const`` - Series - pd_series_groupby.h:220 - :ref:`View ` * - ``Series var(int ddof = 1) const`` - Series - pd_series_groupby.h:550 - :ref:`View ` Aggregation ----------- .. list-table:: :widths: 40 20 15 25 :header-rows: 1 * - Signature - Return Type - Location - Example * - ``Series agg(const std::string& func) const`` - Series - pd_series_groupby.h:634 - :ref:`View ` * - ``DataFrame agg(const std::vector& funcs) const`` - DataFrame - pd_series_groupby.h:708 - :ref:`View ` * - ``pandas::Result agg_with_dtype(const std::string& how) const`` - pandas::Result - pd_series_groupby.h:716 - :ref:`View ` * - ``pandas::Result agg_with_dtype_list(const std::vector& funcs) const`` - pandas::Result - pd_series_groupby.h:722 - :ref:`View ` * - ``auto apply(Func&& func) const -> Series>()))>`` - auto - pd_series_groupby.h:737 - :ref:`View ` * - ``void apply_result_index(Series& result) const`` - void - pd_series_groupby.h:1098 - :ref:`View ` * - ``Series transform(Func&& func) const`` - Series - pd_series_groupby.h:767 - :ref:`View ` Arithmetic ---------- .. list-table:: :widths: 40 20 15 25 :header-rows: 1 * - Signature - Return Type - Location - Example * - ``const std::vector& multiindex_names() const`` - const std::vector& - pd_series_groupby.h:137 - :ref:`View ` Comparison ---------- .. list-table:: :widths: 40 20 15 25 :header-rows: 1 * - Signature - Return Type - Location - Example * - ``std::vector> level_arrays(nlevels)`` - std::vector> - pd_series_groupby.h:1114 - Time Series ----------- .. list-table:: :widths: 40 20 15 25 :header-rows: 1 * - Signature - Return Type - Location - Example * - ``Series diff(int periods = 1) const`` - Series - pd_series_groupby.h:903 - :ref:`View ` * - ``Series shift(int periods = 1) const`` - Series - pd_series_groupby.h:935 - :ref:`View ` Other Methods ------------- .. list-table:: :widths: 40 20 15 25 :header-rows: 1 * - Signature - Return Type - Location - Example * - ``void build_groups()`` - void - pd_series_groupby.h:1136 - * - ``const std::vector& categorical_categories() const`` - const std::vector& - pd_series_groupby.h:143 - :ref:`View ` * - ``Series cumcount(bool ascending = true) const`` - Series - pd_series_groupby.h:991 - * - ``const std::map>& group_indices() const`` - const std::map>& - pd_series_groupby.h:107 - * - ``const std::vector& group_keys_order() const`` - const std::vector& - pd_series_groupby.h:112 - :ref:`View ` * - ``const std::string& grouper_dtype() const`` - const std::string& - pd_series_groupby.h:149 - :ref:`View ` * - ``std::vector groups() const`` - std::vector - pd_series_groupby.h:1046 - :ref:`View ` * - ``const std::map>& indices() const`` - const std::map>& - pd_series_groupby.h:1054 - :ref:`View ` * - ``Series ngroup(bool ascending = true) const`` - Series - pd_series_groupby.h:1015 - * - ``size_t ngroups() const`` - size_t - pd_series_groupby.h:1038 - :ref:`View ` * - ``Series nth(int n, const std::string& dropna_mode = "") const`` - Series - pd_series_groupby.h:419 - :ref:`View ` * - ``const Series& series() const`` - const Series& - pd_series_groupby.h:117 - :ref:`View ` * - ``bool series_name_is_int() const`` - bool - pd_series_groupby.h:125 - * - ``void set_all_categories(const std::vector& categories)`` - void - pd_series_groupby.h:153 - * - ``void set_categorical_categories(const std::vector& cats)`` - void - pd_series_groupby.h:140 - :ref:`View ` * - ``void set_grouper_dtype(const std::string& dtype)`` - void - pd_series_groupby.h:146 - * - ``void set_multiindex_names(const std::vector& names)`` - void - pd_series_groupby.h:134 - :ref:`View ` * - ``void set_series_name(const std::string& name)`` - void - pd_series_groupby.h:123 - * - ``void set_series_name_is_int(bool flag)`` - void - pd_series_groupby.h:124 - * - ``Series size() const`` - Series - pd_series_groupby.h:1082 - :ref:`View ` * - ``std::string source_dtype() const`` - std::string - pd_series_groupby.h:95 - * - ``std::string source_dtype_full() const`` - std::string - pd_series_groupby.h:102 - Code Examples ------------- The following examples are extracted from the test suite. .. _example-seriesgroupby-first-0: .. dropdown:: first (pd_test_1_all.cpp:11616) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 11606 :emphasize-lines: 11 void pd_test_groupby_first_last() { std::cout << "========= GroupBy first/last ===================="; std::map> data = { {"category", {1.0, 1.0, 2.0, 2.0}}, {"value", {10.0, 20.0, 30.0, 40.0}} }; pandas::DataFrame df(data); auto first_result = df.groupby("category").first(); auto last_result = df.groupby("category").last(); // First for group 1: 10, group 2: 30 // Last for group 1: 20, group 2: 40 double first1 = std::stod(first_result["value"].get_value_str(0)); double first2 = std::stod(first_result["value"].get_value_str(1)); bool passed = ((std::abs(first1 - 10.0) < 0.001 && std::abs(first2 - 30.0) < 0.001) || (std::abs(first1 - 30.0) < 0.001 && std::abs(first2 - 10.0) < 0.001)); if (!passed) { .. _example-seriesgroupby-get_group-1: .. dropdown:: get_group (pd_test_2_all.cpp:20487) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 20477 :emphasize-lines: 11 ++g_fail; } } static bool approx_eq(double a, double b, double tol = 1e-9) { if (std::isnan(a) && std::isnan(b)) return true; return std::abs(a - b) < tol; } // ===================================================================== // Test: get_group() with exclude_cols removes groupby columns // ===================================================================== void pd_test_groupby_apply_get_group_exclude() { std::cout << " -- pd_test_groupby_apply_get_group_exclude --" << std::endl; pandas::DataFrame df; df.add_column("key", std::vector{"a", "a", "b", "b"}); df.add_column("val1", std::vector{1.0, 2.0, 3.0, 4.0}); df.add_column("val2", std::vector{10.0, 20.0, 30.0, 40.0}); .. _example-seriesgroupby-get_index_name-2: .. dropdown:: get_index_name (pd_test_3_all.cpp:23398) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 23388 :emphasize-lines: 11 std::vector> level_names = {"first", "second"}; auto mi = pandas::MultiIndex::from_arrays(level_values, level_names); s.set_multiindex(mi); auto gb = s.groupby_by_level(static_cast(0), true); if (gb.group_keys_order().size() != 2) throw std::runtime_error("expected 2 groups"); auto sums = gb.sum(); if (sums[0] != 30.0 || sums[1] != 70.0) throw std::runtime_error("sum mismatch"); if (!gb.get_index_name().has_value() || *gb.get_index_name() != "first") throw std::runtime_error("index name mismatch"); std::cout << " -> tests passed" << std::endl; } void pd_test_groupby_level_multi() { std::cout << "========= groupby_by_level(multi) ====================="; pandas::Series s({1.0, 2.0, 3.0, 4.0}); std::vector> level_values = { .. _example-seriesgroupby-get_index_name-3: .. dropdown:: get_index_name (pd_test_3_all.cpp:23398) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 23388 :emphasize-lines: 11 std::vector> level_names = {"first", "second"}; auto mi = pandas::MultiIndex::from_arrays(level_values, level_names); s.set_multiindex(mi); auto gb = s.groupby_by_level(static_cast(0), true); if (gb.group_keys_order().size() != 2) throw std::runtime_error("expected 2 groups"); auto sums = gb.sum(); if (sums[0] != 30.0 || sums[1] != 70.0) throw std::runtime_error("sum mismatch"); if (!gb.get_index_name().has_value() || *gb.get_index_name() != "first") throw std::runtime_error("index name mismatch"); std::cout << " -> tests passed" << std::endl; } void pd_test_groupby_level_multi() { std::cout << "========= groupby_by_level(multi) ====================="; pandas::Series s({1.0, 2.0, 3.0, 4.0}); std::vector> level_values = { .. _example-seriesgroupby-idxmin_with_dtype-4: .. dropdown:: idxmin_with_dtype (pd_test_5_all.cpp:95397) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 95387 :emphasize-lines: 11 void case_701_dfgb_idxmin_rangeindex(int& local_fail) { std::cout << "-- case_701_dfgb_idxmin_rangeindex\n"; // Default RangeIndex (int64). Result columns must keep int64 dtype. pandas::DataFrame df; df.add_column("v", std::vector{3.0, 1.0, 2.0, 0.5}); df.add_column("key", std::vector{0, 0, 1, 1}); auto gb = df.groupby("key"); pandas::DataFrame out; std::string err; try { out = gb.idxmin_with_dtype(); } catch (const std::exception& e) { err = e.what(); } catch (...) { err = ""; } pandas_tests::check(err.empty(), "C_26_case_701_dfgb_idxmin_rangeindex()_no_throw", local_fail); if (!err.empty()) { std::cout << " err: " << err << "\n"; return; } std::string got = df_col_dtype(out, "v"); bool ok = (got == "int64"); pandas_tests::check(ok, "C_26_case_701_dfgb_idxmin_rangeindex()_dtype", local_fail); if (!ok) std::cout << " got=[" << got << "] expected=[int64]\n"; .. _example-seriesgroupby-last-5: .. dropdown:: last (pd_test_1_all.cpp:11617) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 11607 :emphasize-lines: 11 void pd_test_groupby_first_last() { std::cout << "========= GroupBy first/last ===================="; std::map> data = { {"category", {1.0, 1.0, 2.0, 2.0}}, {"value", {10.0, 20.0, 30.0, 40.0}} }; pandas::DataFrame df(data); auto first_result = df.groupby("category").first(); auto last_result = df.groupby("category").last(); // First for group 1: 10, group 2: 30 // Last for group 1: 20, group 2: 40 double first1 = std::stod(first_result["value"].get_value_str(0)); double first2 = std::stod(first_result["value"].get_value_str(1)); bool passed = ((std::abs(first1 - 10.0) < 0.001 && std::abs(first2 - 30.0) < 0.001) || (std::abs(first1 - 30.0) < 0.001 && std::abs(first2 - 10.0) < 0.001)); if (!passed) { std::cout << " [FAIL] : in pd_test_groupby_first_last() : first values incorrect" << std::endl; .. _example-seriesgroupby-set_index_name-6: .. dropdown:: set_index_name (pd_test_2_all.cpp:20842) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 20832 :emphasize-lines: 11 void test_sgb_apply_result_index_categorical() { std::cout << " -- test_sgb_apply_result_index_categorical --" << std::endl; std::vector values = {5.0, 10.0}; pandas::Series by({"A", "B"}); pandas::Series data(values); auto sgb = data.groupby(by); sgb.set_categorical_categories({"A", "B", "C"}); sgb.set_index_name("cat_key"); pandas::Series result(values); std::vector idx_labels = {"A", "B"}; result.set_index(std::make_unique>(idx_labels)); sgb.apply_result_index(result); // Should have CategoricalIndex (dtype_name() returns "category") check(result.index().dtype_name() == "category", "is_categorical_index"); } .. _example-seriesgroupby-count-7: .. dropdown:: count (pd_test_1_all.cpp:66) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 56 :emphasize-lines: 11 if (arr.is_na(0)) { std::cout << " [FAIL] : in pd_test_boolean_array_na_handling() : is_na(0) should be false" << std::endl; throw std::runtime_error("pd_test_boolean_array_na_handling failed: is_na(0) should be false"); } if (!arr.has_na()) { std::cout << " [FAIL] : in pd_test_boolean_array_na_handling() : has_na() should be true" << std::endl; throw std::runtime_error("pd_test_boolean_array_na_handling failed: has_na() should be true"); } if (arr.count() != 2) { std::cout << " [FAIL] : in pd_test_boolean_array_na_handling() : count() should be 2" << std::endl; throw std::runtime_error("pd_test_boolean_array_na_handling failed: count() should be 2"); } std::cout << " -> tests passed" << std::endl; } void pd_test_boolean_array_kleene_and() { std::cout << "========= BooleanArray: Kleene AND ======================= "; .. _example-seriesgroupby-cummax-8: .. dropdown:: cummax (pd_test_1_all.cpp:5152) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 5142 :emphasize-lines: 11 // cummin: [1, 1, 1, 1] auto cmin = df.cummin(); val = cmin["A"].get_value_str(3); passed = std::abs(std::stod(val) - 1.0) < 0.001; if (!passed) { std::cout << " [FAIL] : in pd_test_arithmetic_dataframe_cumulative() : cummin failed" << std::endl; throw std::runtime_error("pd_test_arithmetic_dataframe_cumulative failed: cummin failed"); } // cummax: [1, 2, 3, 4] auto cmax = df.cummax(); val = cmax["A"].get_value_str(2); passed = std::abs(std::stod(val) - 3.0) < 0.001; if (!passed) { std::cout << " [FAIL] : in pd_test_arithmetic_dataframe_cumulative() : cummax failed" << std::endl; throw std::runtime_error("pd_test_arithmetic_dataframe_cumulative failed: cummax failed"); } std::cout << " -> tests passed" << std::endl; } .. _example-seriesgroupby-cummin-9: .. dropdown:: cummin (pd_test_1_all.cpp:5143) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 5133 :emphasize-lines: 11 // cumprod: [1, 2, 6, 24] auto cp = df.cumprod(); val = cp["A"].get_value_str(3); passed = std::abs(std::stod(val) - 24.0) < 0.001; if (!passed) { std::cout << " [FAIL] : in pd_test_arithmetic_dataframe_cumulative() : cumprod failed" << std::endl; throw std::runtime_error("pd_test_arithmetic_dataframe_cumulative failed: cumprod failed"); } // cummin: [1, 1, 1, 1] auto cmin = df.cummin(); val = cmin["A"].get_value_str(3); passed = std::abs(std::stod(val) - 1.0) < 0.001; if (!passed) { std::cout << " [FAIL] : in pd_test_arithmetic_dataframe_cumulative() : cummin failed" << std::endl; throw std::runtime_error("pd_test_arithmetic_dataframe_cumulative failed: cummin failed"); } // cummax: [1, 2, 3, 4] auto cmax = df.cummax(); val = cmax["A"].get_value_str(2); .. _example-seriesgroupby-cumprod-10: .. dropdown:: cumprod (pd_test_1_all.cpp:5134) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 5124 :emphasize-lines: 11 // cumsum: [1, 3, 6, 10] auto cs = df.cumsum(); std::string val = cs["A"].get_value_str(2); bool passed = std::abs(std::stod(val) - 6.0) < 0.001; if (!passed) { std::cout << " [FAIL] : in pd_test_arithmetic_dataframe_cumulative() : cumsum failed" << std::endl; throw std::runtime_error("pd_test_arithmetic_dataframe_cumulative failed: cumsum failed"); } // cumprod: [1, 2, 6, 24] auto cp = df.cumprod(); val = cp["A"].get_value_str(3); passed = std::abs(std::stod(val) - 24.0) < 0.001; if (!passed) { std::cout << " [FAIL] : in pd_test_arithmetic_dataframe_cumulative() : cumprod failed" << std::endl; throw std::runtime_error("pd_test_arithmetic_dataframe_cumulative failed: cumprod failed"); } // cummin: [1, 1, 1, 1] auto cmin = df.cummin(); val = cmin["A"].get_value_str(3); .. _example-seriesgroupby-cumsum-11: .. dropdown:: cumsum (pd_test_1_all.cpp:5125) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 5115 :emphasize-lines: 11 } void pd_test_arithmetic_dataframe_cumulative() { std::cout << "========= DataFrame cumulative =================="; std::map> data; data["A"] = {1.0, 2.0, 3.0, 4.0}; pandas::DataFrame df(data); // cumsum: [1, 3, 6, 10] auto cs = df.cumsum(); std::string val = cs["A"].get_value_str(2); bool passed = std::abs(std::stod(val) - 6.0) < 0.001; if (!passed) { std::cout << " [FAIL] : in pd_test_arithmetic_dataframe_cumulative() : cumsum failed" << std::endl; throw std::runtime_error("pd_test_arithmetic_dataframe_cumulative failed: cumsum failed"); } // cumprod: [1, 2, 6, 24] auto cp = df.cumprod(); val = cp["A"].get_value_str(3); .. _example-seriesgroupby-max-12: .. dropdown:: max (pd_test_1_all.cpp:771) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 761 :emphasize-lines: 11 pandas::CategoricalArray arr = pandas::CategoricalArray::from_codes(codes, cats, true); // ordered // Test min std::optional min_val = arr.min(); if (!min_val.has_value() || *min_val != "low") { std::cout << " [FAIL] : in pd_test_categorical_array_ordered_operations() : min != 'low'" << std::endl; throw std::runtime_error("pd_test_categorical_array_ordered_operations failed: min != 'low'"); } // Test max std::optional max_val = arr.max(); if (!max_val.has_value() || *max_val != "high") { std::cout << " [FAIL] : in pd_test_categorical_array_ordered_operations() : max != 'high'" << std::endl; throw std::runtime_error("pd_test_categorical_array_ordered_operations failed: max != 'high'"); } // Test unordered throws for min/max pandas::CategoricalArray unordered = arr.as_unordered(); bool threw = false; try { unordered.min(); .. _example-seriesgroupby-mean-13: .. dropdown:: mean (pd_test_1_all.cpp:282) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 272 :emphasize-lines: 11 std::optional(true), std::optional(true) }); auto s = arr.sum(); if (!s.has_value() || s.value() != 3) { std::cout << " [FAIL] : in pd_test_boolean_array_reductions() : sum should be 3" << std::endl; throw std::runtime_error("pd_test_boolean_array_reductions failed: sum"); } auto m = arr.mean(); if (!m.has_value() || std::abs(m.value() - 0.75) > 0.001) { std::cout << " [FAIL] : in pd_test_boolean_array_reductions() : mean should be 0.75" << std::endl; throw std::runtime_error("pd_test_boolean_array_reductions failed: mean"); } std::cout << " -> tests passed" << std::endl; } void pd_test_boolean_array_dtype() { std::cout << "========= BooleanArray: dtype ======================= "; .. _example-seriesgroupby-median-14: .. dropdown:: median (pd_test_1_all.cpp:20910) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 20900 :emphasize-lines: 11 throw std::runtime_error("pd_test_expanding_var failed: expanding var values incorrect"); } std::cout << " -> tests passed" << std::endl; } void pd_test_expanding_median() { std::cout << "========= Expanding median ======================"; pandas::Series s({1.0, 2.0, 3.0, 4.0, 5.0}); auto result = s.expanding().median(); // Expanding median: 1, 1.5, 2, 2.5, 3 bool passed = std::abs(result[0] - 1.0) < 0.001 && std::abs(result[1] - 1.5) < 0.001 && std::abs(result[2] - 2.0) < 0.001 && std::abs(result[3] - 2.5) < 0.001 && std::abs(result[4] - 3.0) < 0.001; if (!passed) { std::cout << " [FAIL] : in pd_test_expanding_median() : expanding median values incorrect" << std::endl; throw std::runtime_error("pd_test_expanding_median failed: expanding median values incorrect"); .. _example-seriesgroupby-min-15: .. dropdown:: min (pd_test_1_all.cpp:764) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 754 :emphasize-lines: 11 } void pd_test_categorical_array_ordered_operations() { std::cout << "========= CategoricalArray: ordered operations (min/max) ======================= "; std::vector cats = {"low", "medium", "high"}; std::vector codes = {0, 2, 1, 0, -1}; // low, high, medium, low, NA pandas::CategoricalArray arr = pandas::CategoricalArray::from_codes(codes, cats, true); // ordered // Test min std::optional min_val = arr.min(); if (!min_val.has_value() || *min_val != "low") { std::cout << " [FAIL] : in pd_test_categorical_array_ordered_operations() : min != 'low'" << std::endl; throw std::runtime_error("pd_test_categorical_array_ordered_operations failed: min != 'low'"); } // Test max std::optional max_val = arr.max(); if (!max_val.has_value() || *max_val != "high") { std::cout << " [FAIL] : in pd_test_categorical_array_ordered_operations() : max != 'high'" << std::endl; throw std::runtime_error("pd_test_categorical_array_ordered_operations failed: max != 'high'"); .. _example-seriesgroupby-nunique-16: .. dropdown:: nunique (pd_test_1_all.cpp:10604) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 10594 :emphasize-lines: 11 std::cout << " -> tests passed" << std::endl; } void pd_test_extension_index_nunique() { std::cout << "========= nunique ========================="; pandas::CategoricalArray arr({"a", "b", "a", "c", "b", std::nullopt}); pandas::CategoricalIndex idx(arr); bool passed = (idx.nunique(true) == 3 && idx.nunique(false) == 4); if (!passed) { std::cout << " [FAIL] : in pd_test_extension_index_nunique() : nunique check failed" << std::endl; throw std::runtime_error("pd_test_extension_index_nunique failed"); } std::cout << " -> tests passed" << std::endl; } void pd_test_extension_index_factorize() { std::cout << "========= factorize ========================="; .. _example-seriesgroupby-std_-17: .. dropdown:: std_ (pd_test_1_all.cpp:20752) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 20742 :emphasize-lines: 11 throw std::runtime_error("pd_test_rolling_min_periods failed: with min_periods=1, idx 1 should be 3.0"); } std::cout << " -> tests passed" << std::endl; } void pd_test_rolling_std() { std::cout << "========= Rolling std ==========================="; pandas::Series s({1.0, 2.0, 3.0, 4.0, 5.0}); auto result = s.rolling(3).std_(); // std([1,2,3]) = 1.0 (ddof=1) // std([2,3,4]) = 1.0 // std([3,4,5]) = 1.0 bool passed = std::abs(result[2] - 1.0) < 0.001; if (!passed) { std::cout << " [FAIL] : in pd_test_rolling_std() : rolling std should be 1.0" << std::endl; throw std::runtime_error("pd_test_rolling_std failed: rolling std should be 1.0"); } .. _example-seriesgroupby-sum-18: .. dropdown:: sum (pd_test_1_all.cpp:276) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 266 :emphasize-lines: 11 } // Test sum/mean pandas::BooleanArray arr({ std::optional(true), std::optional(false), std::optional(true), std::optional(true) }); auto s = arr.sum(); if (!s.has_value() || s.value() != 3) { std::cout << " [FAIL] : in pd_test_boolean_array_reductions() : sum should be 3" << std::endl; throw std::runtime_error("pd_test_boolean_array_reductions failed: sum"); } auto m = arr.mean(); if (!m.has_value() || std::abs(m.value() - 0.75) > 0.001) { std::cout << " [FAIL] : in pd_test_boolean_array_reductions() : mean should be 0.75" << std::endl; throw std::runtime_error("pd_test_boolean_array_reductions failed: mean"); } .. _example-seriesgroupby-sum_int64_bool_-19: .. dropdown:: sum_int64_bool_ (pd_test_5_all.cpp:55457) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 55447 :emphasize-lines: 11 check_col_dtype("caseG3", df, 0, "float64", local_fail); } static void f_seriesgroupby_agg_list_dtype_12_b3d2f7_caseH1_bool_sum(int& local_fail) { std::cout << "-- caseH1_bool_sum\n"; pandas::Series v({true, false, true, true}); pandas::Series by({"a", "a", "b", "b"}); auto sgb = v.groupby(by); pandas::DataFrame df = sgb.agg(std::vector{"sum"}); check_ncols("caseH1", df, 1, local_fail); // Plan 21: pandas widens bool sum to int64 — fixed via sum_int64_bool_(). check_col_dtype("caseH1", df, 0, "int64", local_fail); } static void f_seriesgroupby_agg_list_dtype_12_b3d2f7_caseH2_bool_first(int& local_fail) { std::cout << "-- caseH2_bool_first\n"; pandas::Series v({true, false, true, true}); pandas::Series by({"a", "a", "b", "b"}); auto sgb = v.groupby(by); pandas::DataFrame df = sgb.agg(std::vector{"first"}); check_ncols("caseH2", df, 1, local_fail); .. _example-seriesgroupby-var-20: .. dropdown:: var (pd_test_1_all.cpp:20890) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 20880 :emphasize-lines: 11 throw std::runtime_error("pd_test_expanding_std failed: expanding std values incorrect"); } std::cout << " -> tests passed" << std::endl; } void pd_test_expanding_var() { std::cout << "========= Expanding var ========================="; pandas::Series s({1.0, 2.0, 3.0, 4.0, 5.0}); auto result = s.expanding().var(); // Expanding var (ddof=1): NaN, 0.5, 1.0, 1.6667, 2.5 bool passed = std::isnan(result[0]) && std::abs(result[1] - 0.5) < 0.001 && std::abs(result[2] - 1.0) < 0.001 && std::abs(result[3] - 1.6667) < 0.001 && std::abs(result[4] - 2.5) < 0.001; if (!passed) { std::cout << " [FAIL] : in pd_test_expanding_var() : expanding var values incorrect" << std::endl; throw std::runtime_error("pd_test_expanding_var failed: expanding var values incorrect"); .. _example-seriesgroupby-agg-21: .. dropdown:: agg (pd_test_1_all.cpp:11100) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 11090 :emphasize-lines: 11 } void pd_test_func_apply_series_agg() { std::cout << "========= Series agg =================================="; pandas::Series s({1.0, 2.0, 3.0, 4.0, 5.0}, "values"); bool passed = true; // Test string-based aggregation auto sum_result = s.agg("sum"); if (!sum_result.has_value() || !approx_equal(sum_result.value(), 15.0)) { passed = false; std::cout << " [FAIL] : in pd_test_func_apply_series_agg() : sum failed" << std::endl; throw std::runtime_error("pd_test_func_apply_series_agg failed: sum failed"); } auto mean_result = s.agg("mean"); if (!mean_result.has_value() || !approx_equal(mean_result.value(), 3.0)) { passed = false; std::cout << " [FAIL] : in pd_test_func_apply_series_agg() : mean failed" << std::endl; .. _example-seriesgroupby-agg-22: .. dropdown:: agg (pd_test_1_all.cpp:11100) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 11090 :emphasize-lines: 11 } void pd_test_func_apply_series_agg() { std::cout << "========= Series agg =================================="; pandas::Series s({1.0, 2.0, 3.0, 4.0, 5.0}, "values"); bool passed = true; // Test string-based aggregation auto sum_result = s.agg("sum"); if (!sum_result.has_value() || !approx_equal(sum_result.value(), 15.0)) { passed = false; std::cout << " [FAIL] : in pd_test_func_apply_series_agg() : sum failed" << std::endl; throw std::runtime_error("pd_test_func_apply_series_agg failed: sum failed"); } auto mean_result = s.agg("mean"); if (!mean_result.has_value() || !approx_equal(mean_result.value(), 3.0)) { passed = false; std::cout << " [FAIL] : in pd_test_func_apply_series_agg() : mean failed" << std::endl; .. _example-seriesgroupby-agg_with_dtype-23: .. dropdown:: agg_with_dtype (pd_test_5_all.cpp:94652) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 94642 :emphasize-lines: 11 static void run_dfgb_case(const std::string& fn, const std::string& col, const std::string& expected_dtype, const std::string& label, int& local_fail) { pandas::DataFrame df = make_mixed_df(); auto gb = df.groupby("key"); pandas::DataFrame out; std::string err; try { out = gb.agg_with_dtype(fn); } catch (const std::exception& e) { err = e.what(); } catch (...) { err = ""; } pandas_tests::check(err.empty(), label + "_no_throw", local_fail); if (!err.empty()) { std::cout << " err: " << err << "\n"; .. _example-seriesgroupby-agg_with_dtype_list-24: .. dropdown:: agg_with_dtype_list (pd_test_5_all.cpp:94682) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 94672 :emphasize-lines: 11 static void run_dfgb_list_case(const std::vector& fns, const std::string& src_col, const std::vector& expected, const std::string& label, int& local_fail) { pandas::DataFrame df = make_mixed_df(); auto gb = df.groupby("key"); pandas::DataFrame out; std::string err; try { out = gb.agg_with_dtype_list(fns); } catch (const std::exception& e) { err = e.what(); } catch (...) { err = ""; } pandas_tests::check(err.empty(), label + "_no_throw", local_fail); if (!err.empty()) { std::cout << " err: " << err << "\n"; .. _example-seriesgroupby-apply-25: .. dropdown:: apply (pd_test_1_all.cpp:11244) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 11234 :emphasize-lines: 11 void pd_test_func_apply_dataframe_apply_axis0() { std::cout << "========= DataFrame apply axis=0 ======================"; std::map> data = { {"A", {1.0, 2.0, 3.0}}, {"B", {4.0, 5.0, 6.0}} }; pandas::DataFrame df(data); // apply axis=0 applies function to each column auto result = df.apply([](const std::vector& col) { return std::accumulate(col.begin(), col.end(), 0.0); }, 0); bool passed = true; // Plan F·dtype: axis=0 reduce now returns a single "result" column // with the original column names ("A", "B") as the row index. // Sum of A: 1+2+3=6, Sum of B: 4+5+6=15 const auto& result_col = result["result"]; double sum_a = std::stod(result_col.get_value_str(0)); .. _example-seriesgroupby-apply_result_index-26: .. dropdown:: apply_result_index (pd_test_2_all.cpp:20781) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 20771 :emphasize-lines: 11 pandas::Series by(keys); pandas::Series data(values); auto sgb = data.groupby(by); sgb.set_multiindex_names({"level0", "level1"}); // Create a "result" series with composite index pandas::Series result(values); result.set_index(std::make_unique>(keys)); sgb.apply_result_index(result); // Should now have a MultiIndex check(result.has_multiindex(), "has_multiindex"); check(result.multiindex().nlevels() == 2, "nlevels_2"); } void test_sgb_apply_result_index_3level() { std::cout << " -- test_sgb_apply_result_index_3level --" << std::endl; using std::string; .. _example-seriesgroupby-transform-27: .. dropdown:: transform (pd_test_1_all.cpp:11071) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 11061 :emphasize-lines: 11 std::cout << " -> tests passed" << std::endl; } void pd_test_func_apply_series_transform() { std::cout << "========= Series transform ============================"; pandas::Series s({1.0, 2.0, 3.0, 4.0}, "values"); // Transform must return same shape auto result = s.transform([](double x) { return x * 2 + 1; }); bool passed = true; if (result.size() != s.size()) { passed = false; std::cout << " [FAIL] : in pd_test_func_apply_series_transform() : size changed" << std::endl; throw std::runtime_error("pd_test_func_apply_series_transform failed: size changed"); } std::vector expected = {3.0, 5.0, 7.0, 9.0}; for (size_t i = 0; i < result.size(); ++i) { .. _example-seriesgroupby-multiindex_names-28: .. dropdown:: multiindex_names (pd_test_3_all.cpp:23419) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 23409 :emphasize-lines: 11 {"a", "a", "b", "b"}, {"x", "y", "x", "y"} }; std::vector> level_names = {"L0", "L1"}; auto mi = pandas::MultiIndex::from_arrays(level_values, level_names); s.set_multiindex(mi); std::vector levels = {0, 1}; auto gb = s.groupby_by_level(levels, true); if (gb.group_keys_order().size() != 4) throw std::runtime_error("expected 4 composite groups"); if (gb.multiindex_names().size() != 2 || gb.multiindex_names()[0] != "L0" || gb.multiindex_names()[1] != "L1") throw std::runtime_error("multiindex names mismatch"); std::cout << " -> tests passed" << std::endl; } void pd_test_groupby_by_index() { std::cout << "========= groupby_by_index() =========================="; pandas::Series s({10.0, 20.0, 30.0}); s.set_index(pandas::Index({"a", "b", "a"})); .. _example-seriesgroupby-diff-29: .. dropdown:: diff (pd_test_1_all.cpp:5171) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 5161 :emphasize-lines: 11 } void pd_test_arithmetic_dataframe_diff_shift() { std::cout << "========= DataFrame diff/shift =================="; std::map> data; data["A"] = {1.0, 3.0, 6.0, 10.0}; pandas::DataFrame df(data); // diff: [NaN, 2, 3, 4] auto d = df.diff(); std::string val = d["A"].get_value_str(1); bool passed = std::abs(std::stod(val) - 2.0) < 0.001; if (!passed) { std::cout << " [FAIL] : in pd_test_arithmetic_dataframe_diff_shift() : diff failed" << std::endl; throw std::runtime_error("pd_test_arithmetic_dataframe_diff_shift failed: diff failed"); } // First element should be NaN val = d["A"].get_value_str(0); passed = std::isnan(std::stod(val)); .. _example-seriesgroupby-shift-30: .. dropdown:: shift (pd_test_1_all.cpp:5188) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 5178 :emphasize-lines: 11 // First element should be NaN val = d["A"].get_value_str(0); passed = std::isnan(std::stod(val)); if (!passed) { std::cout << " [FAIL] : in pd_test_arithmetic_dataframe_diff_shift() : diff NaN failed" << std::endl; throw std::runtime_error("pd_test_arithmetic_dataframe_diff_shift failed: diff NaN failed"); } // shift: [NaN, 1, 3, 6] auto s = df.shift(); val = s["A"].get_value_str(1); passed = std::abs(std::stod(val) - 1.0) < 0.001; if (!passed) { std::cout << " [FAIL] : in pd_test_arithmetic_dataframe_diff_shift() : shift failed" << std::endl; throw std::runtime_error("pd_test_arithmetic_dataframe_diff_shift failed: shift failed"); } std::cout << " -> tests passed" << std::endl; } .. _example-seriesgroupby-categorical_categories-31: .. dropdown:: categorical_categories (pd_test_3_all.cpp:23513) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 23503 :emphasize-lines: 11 pandas::CategoricalArray cat({"a", "b", "a"}, {"a", "b", "c"}); auto gb_obs = s.groupby_by_categorical(cat, true, true); if (gb_obs.group_keys_order().size() != 2) throw std::runtime_error("expected 2 observed groups"); auto gb_all = s.groupby_by_categorical(cat, true, false); if (gb_all.group_keys_order().size() != 3) throw std::runtime_error("expected 3 groups with observed=false"); if (gb_obs.categorical_categories().size() != 3) throw std::runtime_error("categorical_categories not set"); std::cout << " -> tests passed" << std::endl; } void pd_test_groupby_by_labels() { std::cout << "========= groupby_by_labels() ========================="; pandas::Series s({1.0, 2.0, 3.0, 4.0}); std::vector labels = {"X", "Y", "X", "Y"}; .. _example-seriesgroupby-group_keys_order-32: .. dropdown:: group_keys_order (pd_test_3_all.cpp:23393) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 23383 :emphasize-lines: 11 pandas::Series s({10.0, 20.0, 30.0, 40.0}); std::vector> level_values = { {"a", "a", "b", "b"}, {"x", "y", "x", "y"} }; std::vector> level_names = {"first", "second"}; auto mi = pandas::MultiIndex::from_arrays(level_values, level_names); s.set_multiindex(mi); auto gb = s.groupby_by_level(static_cast(0), true); if (gb.group_keys_order().size() != 2) throw std::runtime_error("expected 2 groups"); auto sums = gb.sum(); if (sums[0] != 30.0 || sums[1] != 70.0) throw std::runtime_error("sum mismatch"); if (!gb.get_index_name().has_value() || *gb.get_index_name() != "first") throw std::runtime_error("index name mismatch"); std::cout << " -> tests passed" << std::endl; } .. _example-seriesgroupby-grouper_dtype-33: .. dropdown:: grouper_dtype (pd_test_3_all.cpp:23493) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 23483 :emphasize-lines: 11 std::cout << "========= groupby_by_numeric() ========================"; pandas::Series s({10.0, 20.0, 30.0, 40.0}); pandas::Series by_s({1.0, 2.0, 1.0, 2.0}); auto gb = s.groupby_by_numeric(by_s, true); if (gb.group_keys_order().size() != 2) throw std::runtime_error("expected 2 groups"); auto sums = gb.sum(); if (sums[0] != 40.0 || sums[1] != 60.0) throw std::runtime_error("sum mismatch"); if (gb.grouper_dtype() != "float64") throw std::runtime_error("grouper_dtype mismatch"); std::cout << " -> tests passed" << std::endl; } void pd_test_groupby_by_categorical() { std::cout << "========= groupby_by_categorical() ===================="; pandas::Series s({10.0, 20.0, 30.0}); pandas::CategoricalArray cat({"a", "b", "a"}, {"a", "b", "c"}); .. _example-seriesgroupby-groups-34: .. dropdown:: groups (pd_test_2_all.cpp:20864) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 20854 :emphasize-lines: 11 // ===================================================================== // Per-group expanding tests // ===================================================================== void test_series_groupby_expanding_sum() { std::cout << " -- test_series_groupby_expanding_sum --" << std::endl; // Two groups: A=[1,2,3], B=[10,20] std::vector vals = {1.0, 10.0, 2.0, 20.0, 3.0}; pandas::Series data(vals); pandas::Series groups({"A", "B", "A", "B", "A"}); auto sgb = data.groupby(groups); pandas::SeriesGroupByExpandingWindow ew(sgb, 1); auto result = ew.sum(); check(result.size() == 5, "size_5"); // A group: expanding sum = 1, 3, 6 // B group: expanding sum = 10, 30 // Original order: [A:1, B:10, A:3, B:30, A:6] check(approx_eq(result[0], 1.0), "A_exp_sum_0"); .. _example-seriesgroupby-indices-35: .. dropdown:: indices (pd_test_1_all.cpp:14921) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 14911 :emphasize-lines: 11 passed = passed && r2_tup1[0] == "b" && r2_tup1[1] == "x"; passed = passed && r2_tup2[0] == "c" && r2_tup2[1] == "x"; } // Test empty vector (no deletion) std::cout << " Test 3: Empty delete_(std::vector{})..." << std::endl; auto result3 = mi.delete_(std::vector{}); std::cout << " Result size: " << result3.size() << " (expected " << mi.size() << ")" << std::endl; passed = passed && result3.size() == mi.size(); // Test duplicate indices (should be deduplicated) std::cout << " Test 4: Duplicate delete_({1, 1, 2})..." << std::endl; auto result4 = mi.delete_({1, 1, 2}); std::cout << " Result size: " << result4.size() << " (expected 3)" << std::endl; passed = passed && result4.size() == 3; // Test deleting all elements std::cout << " Test 5: Delete all delete_({0,1,2,3,4})..." << std::endl; auto result5 = mi.delete_({0, 1, 2, 3, 4}); std::cout << " Result size: " << result5.size() << " (expected 0)" << std::endl; passed = passed && result5.size() == 0; .. _example-seriesgroupby-ngroups-36: .. dropdown:: ngroups (pd_test_1_all.cpp:11497) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 11487 :emphasize-lines: 11 // Create DataFrame with category column std::map> data = { {"category", {1.0, 1.0, 2.0, 2.0, 2.0}}, {"value", {10.0, 20.0, 30.0, 40.0, 50.0}} }; pandas::DataFrame df(data); // Test groupby auto grouped = df.groupby("category"); bool passed = grouped.ngroups() == 2; if (!passed) { std::cout << " [FAIL] : in pd_test_groupby_basic() : ngroups should be 2" << std::endl; throw std::runtime_error("pd_test_groupby_basic failed: ngroups should be 2"); } std::cout << " -> tests passed" << std::endl; } void pd_test_groupby_multiple_columns() { std::cout << "========= GroupBy multiple columns =============="; .. _example-seriesgroupby-nth-37: .. dropdown:: nth (pd_test_3_all.cpp:27491) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 27481 :emphasize-lines: 11 check(result_cumsum["B"].get_value_double(1) == 2.0, "row 1 (bar) cumsum B = 2"); check(result_cumsum["B"].get_value_double(3) == 6.0, "row 3 (bar) cumsum B = 6"); } void pd_test_gb_nth_basic() { std::cout << " -- pd_test_gb_nth_basic --" << std::endl; auto df = make_test_df(); auto gb = df.groupby("A"); auto result = gb.nth(0); check(result.nrows() == 2, "nth(0) returns 2 rows (one per group)"); auto result_last = gb.nth(-1); check(result_last.nrows() == 2, "nth(-1) returns 2 rows"); auto result_multi = gb.nth(std::vector{0, -1}); check(result_multi.nrows() == 4, "nth([0,-1]) returns 4 rows"); } void pd_test_gb_nth_slice() { .. _example-seriesgroupby-series-38: .. dropdown:: series (pd_test_2_all.cpp:2307) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 2297 :emphasize-lines: 11 std::vector index = {"a", "b", "c", "d", "e"}; std::map> data1; data1["col1"] = {1.0, 2.0, 3.0, 4.0, 5.0}; data1["col2"] = {2.0, 4.0, 6.0, 8.0, 10.0}; // Perfectly correlated with col1 pandas::DataFrame df1(data1, std::make_unique>(index)); // Series with same index and values that correlate with df columns pandas::Series series({1.0, 2.0, 3.0, 4.0, 5.0}); series.set_index(pandas::Index(index)); pandas::Series result = df1.corrwith(series); bool passed = true; // col1 should have correlation 1.0 with series if (!approx_equal(result[0], 1.0)) { std::cout << "\n [FAIL] : Expected correlation 1.0 for col1, got " << result[0] << std::endl; passed = false; } .. _example-seriesgroupby-set_categorical_categories-39: .. dropdown:: set_categorical_categories (pd_test_2_all.cpp:20841) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 20831 :emphasize-lines: 11 } void test_sgb_apply_result_index_categorical() { std::cout << " -- test_sgb_apply_result_index_categorical --" << std::endl; std::vector values = {5.0, 10.0}; pandas::Series by({"A", "B"}); pandas::Series data(values); auto sgb = data.groupby(by); sgb.set_categorical_categories({"A", "B", "C"}); sgb.set_index_name("cat_key"); pandas::Series result(values); std::vector idx_labels = {"A", "B"}; result.set_index(std::make_unique>(idx_labels)); sgb.apply_result_index(result); // Should have CategoricalIndex (dtype_name() returns "category") check(result.index().dtype_name() == "category", "is_categorical_index"); .. _example-seriesgroupby-set_multiindex_names-40: .. dropdown:: set_multiindex_names (pd_test_2_all.cpp:20775) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 20765 :emphasize-lines: 11 // Simulate a 2-level groupby result with composite \x1f keys using std::string; string sep(1, '\x1f'); std::vector keys = {"A" + sep + "X", "A" + sep + "Y", "B" + sep + "X", "B" + sep + "Y"}; std::vector values = {1.0, 2.0, 3.0, 4.0}; pandas::Series by(keys); pandas::Series data(values); auto sgb = data.groupby(by); sgb.set_multiindex_names({"level0", "level1"}); // Create a "result" series with composite index pandas::Series result(values); result.set_index(std::make_unique>(keys)); sgb.apply_result_index(result); // Should now have a MultiIndex check(result.has_multiindex(), "has_multiindex"); check(result.multiindex().nlevels() == 2, "nlevels_2"); .. _example-seriesgroupby-size-41: .. dropdown:: size (pd_test_1_all.cpp:22) :class-title: example-dropdown .. code-block:: cpp :linenos: :lineno-start: 12 :emphasize-lines: 11 #include "../pandas/pd_boolean_array.h" namespace dataframe_tests { namespace dataframe_tests_boolean_array { void pd_test_boolean_array_constructors() { std::cout << "========= BooleanArray: constructors ======================= "; // Default constructor pandas::BooleanArray arr1; if (arr1.size() != 0) { std::cout << " [FAIL] : in pd_test_boolean_array_constructors() : default constructor size != 0" << std::endl; throw std::runtime_error("pd_test_boolean_array_constructors failed: default constructor size != 0"); } // Initializer list constructor pandas::BooleanArray arr2({ std::optional(true), std::optional(false), std::nullopt, std::optional(true)