{"record":{"id":"b90eea82e5543808","repo":"HKUDS/Vibe-Trading","slug":"n-groups-unique-groups-cannot-make-n-folds-fol","errorCode":null,"errorMessage":"{n_groups} unique groups cannot make {n_folds} folds","messagePattern":"(.+?) unique groups cannot make (.+?) folds","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/quantlib/crossvalidation.py","lineNumber":321,"sourceCode":"    if n_folds < MIN_FOLDS:\n        raise ValueError(f\"n_folds must be at least {MIN_FOLDS}, got {n_folds}\")\n    if not 0.0 <= embargo_fraction < 1.0:\n        raise ValueError(f\"embargo_fraction must be in [0, 1), got {embargo_fraction}\")\n\n    grp_array = np.asarray(groups)\n    n_samples = len(grp_array)\n    if n_samples == 0:\n        raise ValueError(\"groups array cannot be empty\")\n\n    # Find unique groups preserving chronological order of appearance\n    unique_groups, first_indices = np.unique(grp_array, return_index=True)\n    # Sort by appearance order\n    order = np.argsort(first_indices)\n    unique_groups = unique_groups[order]\n    n_groups = len(unique_groups)\n\n    if n_groups < n_folds:\n        raise ValueError(f\"{n_groups} unique groups cannot make {n_folds} folds\")\n\n    # Map each group to its member row indices\n    group_to_rows: dict[object, np.ndarray] = {}\n    for idx, g in enumerate(grp_array):\n        group_to_rows.setdefault(g, []).append(idx)\n    for g in group_to_rows:\n        group_to_rows[g] = np.array(group_to_rows[g], dtype=int)\n\n    embargo_groups = int(round(n_groups * embargo_fraction))\n    boundaries = np.linspace(0, n_groups, n_folds + 1).astype(int)\n\n    for fold in range(n_folds):\n        start_g, stop_g = int(boundaries[fold]), int(boundaries[fold + 1])\n        if stop_g <= start_g:\n            continue\n\n        test_groups = set(unique_groups[start_g:stop_g])\n        embargo_end_g = min(n_groups, stop_g + embargo_groups)","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/quantlib/crossvalidation.py#L303-L339","documentation":"group_purged_kfold_splits forms folds from whole groups, so it needs at least n_folds unique groups; each fold's test set is one or more groups. Fewer unique groups than folds makes the split impossible and is rejected.","triggerScenarios":"Calling with groups containing 3 unique values but n_folds=5, e.g. groups=[1,1,2,2,3,3] with n_folds=5, or a grouping column that is constant.","commonSituations":"Grouping by a low-cardinality column (weekday with weekends removed -> 5 values max), a constant/buggy group key, or raising n_folds for a panel with few entities.","solutions":["Lower n_folds to at most the number of unique groups","Choose a higher-cardinality grouping (e.g. per-date instead of per-ticker) if more folds are needed","Fix the group key if it is accidentally constant (wrong column, dtype mismatch after merge)"],"exampleFix":"// before\nsplits = list(group_purged_kfold_splits(X, groups=df['asset_class'], n_folds=6))  # 4 unique classes\n// after\nn_groups = df['asset_class'].nunique()\nsplits = list(group_purged_kfold_splits(X, groups=df['asset_class'], n_folds=min(6, n_groups)))","handlingStrategy":"validation","validationCode":"n_groups = len(set(groups))\nassert n_groups >= n_folds, f'{n_groups} groups cannot make {n_folds} folds'","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use n_folds = min(cfg.n_folds, pd.Series(groups).nunique())","Pick group keys with cardinality comfortably above the fold count"],"tags":["crossvalidation","group-kfold","insufficient-groups","quantlib"],"backgroundTag":"insufficient-groups-for-grouped-cv","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}