From df50956923fbd3a657a326125d27b54cad8a5a5e Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 23 Mar 2021 11:13:50 -0700 Subject: [PATCH 1/5] Fix display of non-numeric DF index columns --- news/2 Fixes/5253.md | 1 + .../dataframes/vscodeDataFrame.py | 12 ++++++++++++ .../data-explorer/mainPanel.tsx | 19 ++++++++++--------- .../data-explorer/reactSlickGrid.tsx | 4 ++-- 4 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 news/2 Fixes/5253.md 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/src/datascience-ui/data-explorer/mainPanel.tsx b/src/datascience-ui/data-explorer/mainPanel.tsx index dbc4db2bbcc..047062c56e2 100644 --- a/src/datascience-ui/data-explorer/mainPanel.tsx +++ b/src/datascience-ui/data-explorer/mainPanel.tsx @@ -37,6 +37,7 @@ 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 = 'No.'; // Our css has to come after in order to override body styles export interface IMainPanelProps { @@ -257,7 +258,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,13 +385,13 @@ 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); + // Generate a column for row numbers + const rowNumberColumn = { + key: RowNumberColumnName, + type: ColumnType.Number + }; + const columns = [rowNumberColumn].concat(variable.columns); return columns.map((c: { key: string; type: ColumnType }, i: number) => { return { type: c.type, @@ -416,7 +417,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..5b6cce030ef 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; @@ -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(
Date: Tue, 23 Mar 2021 17:25:13 -0700 Subject: [PATCH 2/5] Show index column only for DataFrame and Series --- .../data-explorer/mainPanel.tsx | 24 +++++++++++-------- .../dataviewer.functional.test.tsx | 14 +++++++++-- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/datascience-ui/data-explorer/mainPanel.tsx b/src/datascience-ui/data-explorer/mainPanel.tsx index 047062c56e2..e815d0ff6d8 100644 --- a/src/datascience-ui/data-explorer/mainPanel.tsx +++ b/src/datascience-ui/data-explorer/mainPanel.tsx @@ -392,16 +392,20 @@ export class MainPanel extends React.Component type: ColumnType.Number }; const columns = [rowNumberColumn].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 - }; - }); + 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 []; } diff --git a/src/test/datascience/dataviewer.functional.test.tsx b/src/test/datascience/dataviewer.functional.test.tsx index fbe181ad3fe..1aa4c8fe8e2 100644 --- a/src/test/datascience/dataviewer.functional.test.tsx +++ b/src/test/datascience/dataviewer.functional.test.tsx @@ -284,7 +284,17 @@ 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('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 +314,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) => { From 79b002b9e726f26148523dbf267f2b42d7794320 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 23 Mar 2021 17:46:45 -0700 Subject: [PATCH 3/5] Prettier --- .../data-explorer/mainPanel.tsx | 35 +++++++++++-------- .../dataviewer.functional.test.tsx | 6 ++-- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/datascience-ui/data-explorer/mainPanel.tsx b/src/datascience-ui/data-explorer/mainPanel.tsx index e815d0ff6d8..1dcfd8dfb08 100644 --- a/src/datascience-ui/data-explorer/mainPanel.tsx +++ b/src/datascience-ui/data-explorer/mainPanel.tsx @@ -392,20 +392,27 @@ export class MainPanel extends React.Component 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 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 []; } diff --git a/src/test/datascience/dataviewer.functional.test.tsx b/src/test/datascience/dataviewer.functional.test.tsx index 1aa4c8fe8e2..8f78f33349f 100644 --- a/src/test/datascience/dataviewer.functional.test.tsx +++ b/src/test/datascience/dataviewer.functional.test.tsx @@ -288,13 +288,15 @@ suite('DataScience DataViewer tests', () => { }); 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()'); + 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"]); + verifyRows(wrapper.wrapper, [0, 'Name', 'tom', 'nick', 'juli', 1, 'Age', '10', '15', '14']); }); runMountedTest('List', async (wrapper) => { From 71625798f56ccbb196e3f7489a815799fc72fdd9 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 24 Mar 2021 09:16:23 -0700 Subject: [PATCH 4/5] Use a uuid for row column name to avoid conflicts with user column names --- src/datascience-ui/data-explorer/mainPanel.tsx | 3 ++- src/datascience-ui/data-explorer/reactSlickGrid.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/datascience-ui/data-explorer/mainPanel.tsx b/src/datascience-ui/data-explorer/mainPanel.tsx index 1dcfd8dfb08..e71d6f5a292 100644 --- a/src/datascience-ui/data-explorer/mainPanel.tsx +++ b/src/datascience-ui/data-explorer/mainPanel.tsx @@ -32,12 +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 = 'No.'; +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 { diff --git a/src/datascience-ui/data-explorer/reactSlickGrid.tsx b/src/datascience-ui/data-explorer/reactSlickGrid.tsx index 5b6cce030ef..a0cd69f3fc7 100644 --- a/src/datascience-ui/data-explorer/reactSlickGrid.tsx +++ b/src/datascience-ui/data-explorer/reactSlickGrid.tsx @@ -520,7 +520,6 @@ export class ReactSlickGrid extends React.Component[]) => { @@ -535,6 +534,7 @@ export class ReactSlickGrid extends React.Component { From 209081b6720b1f20a814dae954242506be9bef1a Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 24 Mar 2021 10:42:46 -0700 Subject: [PATCH 5/5] More safety accessing index name --- .../getJupyterVariableDataFrameInfo.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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