diff --git a/news/2 Fixes/5253.md b/news/2 Fixes/5253.md new file mode 100644 index 00000000000..5aadd167436 --- /dev/null +++ b/news/2 Fixes/5253.md @@ -0,0 +1 @@ +Fix data viewer display of non-numeric index columns in DataFrames. \ No newline at end of file diff --git a/pythonFiles/vscode_datascience_helpers/dataframes/vscodeDataFrame.py b/pythonFiles/vscode_datascience_helpers/dataframes/vscodeDataFrame.py index 9b6800900b5..6b15cd5e3e3 100644 --- a/pythonFiles/vscode_datascience_helpers/dataframes/vscodeDataFrame.py +++ b/pythonFiles/vscode_datascience_helpers/dataframes/vscodeDataFrame.py @@ -170,6 +170,17 @@ def _VSCODE_getDataFrameInfo(df): columnTypes = _VSCODE_builtins.list(df.dtypes) + # Compute the index column. It may have been renamed + try: + indexColumn = df.index.name if df.index.name else "index" + except AttributeError: + indexColumn = "index" + + # Make sure the index column exists + if indexColumn not in columnNames: + columnNames.insert(0, indexColumn) + columnTypes.insert(0, "int64") + # Then loop and generate our output json columns = [] for n in _VSCODE_builtins.range(0, _VSCODE_builtins.len(columnNames)): @@ -184,6 +195,7 @@ def _VSCODE_getDataFrameInfo(df): # Save this in our target target = {} target["columns"] = columns + target["indexColumn"] = indexColumn target["rowCount"] = rowCount # return our json object as a string diff --git a/pythonFiles/vscode_datascience_helpers/getJupyterVariableDataFrameInfo.py b/pythonFiles/vscode_datascience_helpers/getJupyterVariableDataFrameInfo.py index d46666018c4..6bc911e380c 100644 --- a/pythonFiles/vscode_datascience_helpers/getJupyterVariableDataFrameInfo.py +++ b/pythonFiles/vscode_datascience_helpers/getJupyterVariableDataFrameInfo.py @@ -78,7 +78,13 @@ def _VSCODE_getRowCount(var): _VSCODE_columnNames = list(_VSCODE_df) # Compute the index column. It may have been renamed - _VSCODE_indexColumn = _VSCODE_df.index.name if _VSCODE_df.index.name else "index" + try: + _VSCODE_indexColumn = ( + _VSCODE_df.index.name if _VSCODE_df.index.name else "index" + ) + except AttributeError: + _VSCODE_indexColumn = "index" + _VSCODE_columnTypes = _VSCODE_builtins.list(_VSCODE_df.dtypes) del _VSCODE_df diff --git a/src/datascience-ui/data-explorer/mainPanel.tsx b/src/datascience-ui/data-explorer/mainPanel.tsx index dbc4db2bbcc..e71d6f5a292 100644 --- a/src/datascience-ui/data-explorer/mainPanel.tsx +++ b/src/datascience-ui/data-explorer/mainPanel.tsx @@ -32,11 +32,13 @@ import '../react-common/codicon/codicon.css'; import '../react-common/seti/seti.less'; import { SliceControl } from './sliceControl'; import { debounce } from 'lodash'; +import * as uuid from 'uuid/v4'; import { initializeIcons } from '@fluentui/react'; initializeIcons(); // Register all FluentUI icons being used to prevent developer console errors const SliceableTypes: Set = new Set(['ndarray', 'Tensor', 'EagerTensor']); +const RowNumberColumnName = uuid(); // Unique key for our column containing row numbers // Our css has to come after in order to override body styles export interface IMainPanelProps { @@ -257,7 +259,7 @@ export class MainPanel extends React.Component gridRows: newActual }); - // Tell our grid about the new ros + // Tell our grid about the new rows this.updateRows(normalized); // Get the next chunk @@ -384,23 +386,34 @@ export class MainPanel extends React.Component } private generateColumns(variable: IDataFrameInfo): Slick.Column[] { - // Generate an index column - const indexColumn = { - key: this.state.indexColumn, - type: ColumnType.Number - }; if (variable.columns) { - const columns = [indexColumn].concat(variable.columns); - return columns.map((c: { key: string; type: ColumnType }, i: number) => { - return { - type: c.type, - field: c.key.toString(), - id: `${i}`, - name: c.key.toString(), - sortable: true, - formatter: cellFormatterFunc - }; - }); + // Generate a column for row numbers + const rowNumberColumn = { + key: RowNumberColumnName, + type: ColumnType.Number + }; + const columns = [rowNumberColumn].concat(variable.columns); + return columns.reduce( + (accum: Slick.Column[], c: { key: string; type: ColumnType }, i: number) => { + // Only show index column for pandas DataFrame and Series + if ( + variable?.type === 'DataFrame' || + variable?.type === 'Series' || + c.key !== this.state.indexColumn + ) { + accum.push({ + type: c.type, + field: c.key.toString(), + id: `${i}`, + name: c.key.toString(), + sortable: true, + formatter: cellFormatterFunc + } as Slick.Column); + } + return accum; + }, + [] + ); } return []; } @@ -416,7 +429,7 @@ export class MainPanel extends React.Component if (!r) { r = {}; } - r[this.state.indexColumn] = this.state.fetchedRowCount + idx; + r[RowNumberColumnName] = this.state.fetchedRowCount + idx; for (let [key, value] of Object.entries(r)) { switch (value) { case 'nan': diff --git a/src/datascience-ui/data-explorer/reactSlickGrid.tsx b/src/datascience-ui/data-explorer/reactSlickGrid.tsx index 4695fb67b2d..a0cd69f3fc7 100644 --- a/src/datascience-ui/data-explorer/reactSlickGrid.tsx +++ b/src/datascience-ui/data-explorer/reactSlickGrid.tsx @@ -473,7 +473,7 @@ export class ReactSlickGrid extends React.Component { - if (c.id !== '0') { + if (c.field !== this.props.idProperty) { c.width = maxFieldWidth; } else { c.width = maxFieldWidth / 2; @@ -520,7 +520,6 @@ export class ReactSlickGrid extends React.Component[]) => { @@ -535,6 +534,7 @@ export class ReactSlickGrid extends React.Component { @@ -573,7 +573,7 @@ export class ReactSlickGrid extends React.Component) => { - if (args.column.id === '0') { + if (args.column.field === this.props.idProperty) { const tooltipText = getLocString('DataScience.clearFilters', 'Clear all filters'); ReactDOM.render(
{ assert.ok(dv, 'DataViewer not created'); await gotAllRows; - verifyRows(wrapper.wrapper, [0, 0, 1, 1, 2, 2, 3, 3]); + verifyRows(wrapper.wrapper, [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3]); + }); + + runMountedTest('Transposed Data Frame', async (wrapper) => { + await injectCode( + 'import pandas as pd\r\ndata = [["tom", 10], ["nick", 15], ["juli", 14]]\r\ndf = pd.DataFrame(data, columns=["Name", "Age"])\r\ndf = df.transpose()' + ); + const gotAllRows = getCompletedPromise(wrapper); + const dv = await createJupyterVariableDataViewer('df', 'DataFrame'); + assert.ok(dv, 'DataViewer not created'); + await gotAllRows; + + verifyRows(wrapper.wrapper, [0, 'Name', 'tom', 'nick', 'juli', 1, 'Age', '10', '15', '14']); }); runMountedTest('List', async (wrapper) => { @@ -304,7 +316,7 @@ suite('DataScience DataViewer tests', () => { assert.ok(dv, 'DataViewer not created'); await gotAllRows; - verifyRows(wrapper.wrapper, [0, 0, 1, 1, 2, 2, 3, 3]); + verifyRows(wrapper.wrapper, [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3]); }); runMountedTest('np.array', async (wrapper) => {