{"record":{"id":"df1ba3affab792e7","repo":"larksuite/cli","slug":"column-labels-collide-after-str-conversion-rena","errorCode":null,"errorMessage":"column labels collide after str() conversion; rename the DataFrame columns before packing","messagePattern":"column labels collide after str\\(\\) conversion; rename the DataFrame columns before packing","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/lark-sheets/scripts/sheets_df.py","lineNumber":34,"sourceCode":"\nimport pandas as pd\n\n\ndef df_to_sheet(df, name, formats=None):\n    \"\"\"Pack one DataFrame into one entry of a `+table-put --sheets` payload.\"\"\"\n    packed = json.loads(df.to_json(orient=\"split\", date_format=\"iso\"))\n    # The protocol requires string column names. pandas keeps integer labels\n    # (e.g. the default RangeIndex columns 0/1/2) as JSON numbers, while the\n    # dtypes dict keys get stringified during JSON serialization — the CLI\n    # then rejects `columns` (\"cannot unmarshal number into … type string\")\n    # and the dtype lookup would miss anyway. Stringify every key once, and\n    # refuse to continue when that conversion silently merges two columns.\n    normalized_labels = [str(c) for c in df.columns]\n    columns = [str(c) for c in packed[\"columns\"]]\n    if normalized_labels != columns:\n        columns = normalized_labels\n    if len(set(columns)) != len(columns):\n        raise ValueError(\n            \"column labels collide after str() conversion; \"\n            \"rename the DataFrame columns before packing\"\n        )\n    packed[\"columns\"] = columns\n    dtype_values = list(df.dtypes)\n    return {\n        \"name\": name,\n        **packed,\n        \"dtypes\": {key: str(dtype) for key, dtype in zip(columns, dtype_values)},\n        **({\"formats\": {str(k): v for k, v in formats.items()}} if formats else {}),\n    }\n\n\ndef sheet_to_df(sheet):\n    \"\"\"Restore one `+table-get` sheet dict into a typed DataFrame.\"\"\"\n    return pd.DataFrame(sheet[\"data\"], columns=sheet[\"columns\"]).astype(sheet[\"dtypes\"])\n","sourceCodeStart":16,"sourceCodeEnd":51,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/skills/lark-sheets/scripts/sheets_df.py#L16-L51","documentation":"df_to_sheet serializes a pandas DataFrame into the Feishu sheet protocol, which requires string column names. Because every column label is converted with str(), two distinct labels can normalize to the same string (e.g. integer 0 and string '0', or True and 'True'); the packed JSON would then contain duplicate columns and the dtype lookup would silently merge them, so the helper refuses to continue with this ValueError.","triggerScenarios":"Calling df_to_sheet(df, name) where df.columns contains labels that map to the same string under str(): mixed int/str labels like 0 and '0', bool True alongside 'True', float 1.0 with '1.0', or any two labels whose str() representations are equal.","commonSituations":"Building DataFrames from mixed-type sources (JSON payloads, Excel imports with numeric headers, RangeIndex default columns combined with renamed string columns) before packing; merging DataFrames where one side has numeric column labels and the other string labels.","solutions":["Rename the DataFrame columns so every label is a unique string before calling df_to_sheet, e.g. df.columns = [str(c) for c in df.columns] followed by disambiguating duplicates.","Audit df.columns for collisions: check that [str(c) for c in df.columns] has the same length as set(...) and fix the offending labels.","Cast numeric/boolean column labels to unique prefixed strings at data-load time (e.g. 'col_0', 'col_1') so str() conversion cannot merge them.","If duplicates are intentional data, pivot or transpose so they become row values instead of column labels."],"exampleFix":"// before\ndf = pd.DataFrame([[1, 2]], columns=[0, \"0\"])\npacked = df_to_sheet(df, \"t\")  # ValueError: labels collide after str()\n\n// after\ndf = pd.DataFrame([[1, 2]], columns=[\"count_a\", \"count_b\"])\n# or programmatic dedupe:\ndf.columns = [f\"col_{c}\" for c in range(df.shape[1])]\npacked = df_to_sheet(df, \"t\")","handlingStrategy":"validation","validationCode":"labels = [str(c) for c in df.columns]\nif len(set(labels)) != len(labels):\n    dupes = sorted({c for c in labels if labels.count(c) > 1})\n    raise ValueError(f\"columns collide after str(): {dupes}\")\n# optionally apply proactively:\n# df.columns = labels","typeGuard":null,"tryCatchPattern":"try:\n    packed = df_to_sheet(df, name)\nexcept ValueError as err:\n    if \"column labels collide\" in str(err):\n        df = df.copy()\n        df.columns = [f\"col_{i}_{c}\" for i, c in enumerate(df.columns)]\n        packed = df_to_sheet(df, name)\n    else:\n        raise","preventionTips":["Always assign plain unique string column labels before packing: df.columns = [str(c) for c in df.columns].","Never mix numeric and string label types in one DataFrame destined for the sheet protocol.","Add a df_to_sheet precheck assertion in data-preparation pipelines.","Avoid default RangeIndex columns; name every column explicitly."],"tags":["pandas","dataframe","sheets","input-validation"],"backgroundTag":"duplicate-column-labels","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}