express.render.data_frame
self, fn) express.render.data_frame(
Decorator for a function that returns a pandas, polars, or eager narwhals
compatible DataFrame
object to render as an interactive table or grid. Features fast virtualized scrolling, sorting, filtering, and row selection (single or multiple).
Returns
:
-
A decorator for a function that returns any of the following: 1. A DataGrid or DataTable object, which can be used to customize the appearance and behavior of the data frame output. 2. A pandas, polars, or eager
narwhals
compatibleDataFrame
object. This object will be internally upgraded to a defaultshiny.express.render.DataGrid(df)
.
Row selection
When using the row selection feature, you can access the selected rows by using the <data_frame_renderer>.cell_selection()
method, where <data_frame_renderer>
is the @render.data_frame
function name that corresponds with the id=
used in outout_data_frame
. Internally, <data_frame_renderer>.cell_selection()
retrieves the selected cell information from session’s input.<data_frame_renderer>_cell_selection()
value and upgrades it for consistent subsetting.
For example, to filter your pandas data frame (df
) down to the selected rows you can use:
df.iloc[list(input.<data_frame_renderer>_cell_selection()["rows"])]
df.iloc[list(<data_frame_renderer>.cell_selection()["rows"])]
<data_frame_renderer>.data_view(selected=True)
The last method (.data_view(selected=True)
) will also apply any sorting, filtering, or edits that has been applied by the user.
Editing cells
When a returned DataTable
or DataGrid
object has editable=True
, app users will be able to edit the cells in the table. After a cell has been edited, the edited value will be sent to the server for processing. The handling methods are set via @<data_frame_renderer>.set_patch_fn
or @<data_frame_renderer>.set_patches_fn
decorators. By default, both decorators will return the corresponding value as a string.
Data methods
There are several methods available to inspect and update data frame renderer. It is important to know the side effects of each method to know how they interact with each other.
- Data frame render method:
- When this method is reactively executed, the
.data()
data frame is set to the underlying data frame and all.cell_patches()
are removed. - When this method is reactively executed, all user state is reset. This includes the user’s edits, sorting, filtering.
- When this method is reactively executed, the
.data()
:- Reactive calculation that returns the render method’s underlying data frame or the data frame supplied to
.update_data(data)
, whichever has been most recently set.
- Reactive calculation that returns the render method’s underlying data frame or the data frame supplied to
.cell_patches()
:- Reactive calculation that returns a list of user edits (or updated cell values) that will be applied to
.data()
to create the.data_patched()
data frame.
- Reactive calculation that returns a list of user edits (or updated cell values) that will be applied to
.data_patched()
:- Reactive calculation that returns the
.data()
data frame with all.cell_patches()
patches applied.
- Reactive calculation that returns the
.data_view(*, selected: bool)
:- Reactive function that returns the
.data_patched()
data frame with the user’s sorting and filtering applied. It represents the data frame as viewed by the user within the browser. - If
selected=True
, only the selected rows are returned.
- Reactive function that returns the
.update_cell_value(value, row, col)
:- Sets a new entry in
.cell_patches()
. - Calling this method will not reset the user’s sorting or filtering.
- Sets a new entry in
.update_data(data)
:- Updates the
.data()
data frame with new data. - Calling this method will remove all
.cell_patches()
. - Calling this method will not reset the user’s sorting or filtering.
- Updates the
Note: All data methods are shallow copies of each other. If they are mutated in place, it will modify the underlying data object and possibly alter other data objects.
Narwhals
Shiny uses narwhals
to manage data frame interactions. From their website: “Extremely lightweight and extensible compatibility layer between dataframe libraries!”. This allows for seamless integration between pandas, polars, and any other eagerly defined data frame type.
There are some reasonable limitations to the narwhals compatibility layer. As they are found, they will be added to this list: * When converting the column type who does not have a 1:1 mapping between libraries (such as pandas’ columns containing str
and dict
items both share the same object
data type), narwhals will only inspect the first row to disambiguate the cell type. This could lead to false negatives in the data type conversion. Shiny could inspect each column in an attempt to disambiguate the cell type, but this would be a costly operation. The best way to avoid this is to use consistent typing. For example, if your first row of the pandas column contains a string and the second row of the same column contains a ui.TagList
, the column will incorrectly be interpreted as a string. To get around this, you can wrap all cells (or at the very lest the first cell) in the same column within a ui.TagList
as it will not insert any tags, but it will cause the column to be interpreted as html
where possible. (tl/dr: Use consistent typing in your columns!)
Tip
This decorator should be applied before the @output
decorator (if that decorator is used). Also, the name of the decorated function (or @output(id=...)
) should match the id
of a output_data_frame
container (see output_data_frame
for example usage).
See Also
Examples
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 400
## file: app.py
import pandas # noqa: F401 (this line needed for Shinylive to load plotly.express)
import plotly.express as px
from shinywidgets import render_widget
from shiny import reactive, req
from shiny.express import render, ui
# Load the Gapminder dataset
df = px.data.gapminder()
# Prepare a summary DataFrame
summary_df = (
df.groupby("country")
.agg(
{
"pop": ["min", "max", "mean"],
"lifeExp": ["min", "max", "mean"],
"gdpPercap": ["min", "max", "mean"],
}
)
.reset_index()
)
summary_df.columns = ["_".join(col).strip() for col in summary_df.columns.values]
summary_df.rename(columns={"country_": "country"}, inplace=True)
# Set up the UI
ui.page_opts(fillable=True)
ui.markdown(
"**Instructions**: Select one or more countries in the table below to see more information."
)
with ui.layout_columns(col_widths=[12, 6, 6]):
with ui.card(height="400px"):
@render.data_frame
def summary_data():
return render.DataGrid(summary_df.round(2), selection_mode="rows")
with ui.card(height="400px"):
@render_widget
def country_detail_pop():
return px.line(
filtered_df(),
x="year",
y="pop",
color="country",
title="Population Over Time",
)
with ui.card(height="400px"):
@render_widget
def country_detail_percap():
return px.line(
filtered_df(),
x="year",
y="gdpPercap",
color="country",
title="GDP per Capita Over Time",
)
@reactive.calc
def filtered_df():
data_selected = summary_data.data_view(selected=True)
req(not data_selected.empty)
countries = data_selected["country"]
# Filter data for selected countries
return df[df["country"].isin(countries)]
Methods
Name | Description |
---|---|
cell_patches | Reactive calculation of the data frame’s edits. |
cell_selection | Reactive calculation of selected cell information. |
data | Reactive calculation of the data frame’s data. |
data_patched | Reactive calculation of the data frame’s patched data. |
data_view | Reactive function that retrieves the data how it is viewed within the browser. |
data_view_rows | Reactive calculation of the data frame’s user view row numbers. |
filter | Reactive calculation of the data frame’s column filters. |
input_cell_selection | [Deprecated] Reactive calculation of selected cell information. |
selection_modes | Reactive calculation of the data frame’s possible selection modes. |
set_patch_fn | Decorator to set the function that updates a single cell in the data frame. |
set_patches_fn | Decorator to set the function that updates a batch of cells in the data frame. |
sort | Reactive calculation of the data frame’s column sorting information. |
update_cell_selection | Update the cell selection in the data frame. |
update_cell_value | Update the value of a cell in the data frame. |
update_data | Update the data frame with new data. |
update_filter | Update the column filtering in the data frame. |
update_sort | Update the column sorting in the data frame. |
cell_patches
express.render.data_frame.cell_patches()
Reactive calculation of the data frame's edits.
This reactive calculation that returns a list of user edits (or updated cell values) that will be applied to .data()
to create the .data_patched()
data frame.
Returns
:
list
[CellPatch
]-
A list of cell patches to apply to the data frame.
cell_selection
express.render.data_frame.cell_selection()
Reactive calculation of selected cell information.
This method is a wrapper around input.<id>_cell_selection()
, where <id>
is the id
of the data frame output. This method returns the selected rows and will cause reactive updates as the selected rows change.
The value has been enhanced from it's vanilla form to include the missing cols
key (or rows
key) as a tuple of integers representing all column (or row) numbers. This allows for consistent usage within code when subsetting your data. These missing keys are not sent over the wire as they are independent of the selection.
Returns
:
CellSelection
-
CellSelection
representing the indices of the selected cells. If no cells are currently selected,None
is returned.
data
express.render.data_frame.data()
Reactive calculation of the data frame's data.
This reactive calculation returns the render method's underlying data frame or the data frame supplied to .update_data(data)
, whichever has been most recently set.
The returned value is a shallow copy of the original data frame. It is possible that alterations to the .data()
data frame could alter other associated data frame values. Please be cautious when using this value directly.
Returns
:
IntoDataFrameT
-
This reactive calculation returns the render method’s underlying data frame or the data frame supplied to
.update_data(data)
, whichever has been most recently set.
data_patched
express.render.data_frame.data_patched()
Reactive calculation of the data frame's patched data.
This method returns the .data()
data frame with all .cell_patches()
patches applied.
The returned value is a shallow copy of the original data frame. It is possible that alterations to the .data_patched()
data frame could alter other associated data frame values. Please be cautious when using this value directly.
Returns
:
IntoDataFrameT
-
The patched data frame.
data_view
=False) express.render.data_frame.data_view(selected
Reactive function that retrieves the data how it is viewed within the browser.
This function will return the .data_patched()
data frame with the user's sorting and filtering applied. It represents the data frame as viewed by the user within the browser.
The returned value is a shallow copy of the original data frame. It is possible that alterations to the .data_view()
data frame could alter other associated date frame values. Please be cautious when using this value directly.
Parameters
selected : bool = False
-
If
True
, subset the viewed data to the selected area. Defaults toFalse
(all rows).
Returns
:
IntoDataFrameT
-
A view of the (possibly selected) data frame as seen in the browser.
See Also
Examples
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 400
## file: app.py
from shared import mtcars
from shiny import reactive
from shiny.express import render, ui
df = reactive.value(mtcars.iloc[:, range(4)])
with ui.layout_columns(col_widths=[4, 4, 4]):
with ui.card():
with ui.card_header():
ui.markdown(
"""
##### Editable data frame
* Edit the cells!
* Sort the columns!
"""
)
@render.data_frame
def df_original():
return render.DataGrid(
df(),
editable=True,
)
# Convert edited values to the correct data type
@df_original.set_patch_fn
def _(*, patch: render.CellPatch) -> render.CellValue:
if patch["column_index"] in [0, 2]:
return float(patch["value"])
return int(patch["value"])
with ui.card():
with ui.card_header():
ui.markdown(
"""
##### Updated data from the first data frame
* Select the rows!
* Filter and sort the columns!
"""
)
@render.data_frame
def df_edited():
return render.DataGrid(
df_original.data_view(),
selection_mode="rows",
filters=True,
)
with ui.card():
with ui.card_header():
ui.markdown(
"""
##### Selected data from the second data frame
* Sort the columns!
"""
)
@render.data_frame
def df_selected():
return render.DataGrid(
df_edited.data_view(selected=True),
selection_mode="none",
)
## file: mtcars.csv
mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
21,6,160,110,3.9,2.62,16.46,0,1,4,4
21,6,160,110,3.9,2.875,17.02,0,1,4,4
22.8,4,108,93,3.85,2.32,18.61,1,1,4,1
21.4,6,258,110,3.08,3.215,19.44,1,0,3,1
18.7,8,360,175,3.15,3.44,17.02,0,0,3,2
18.1,6,225,105,2.76,3.46,20.22,1,0,3,1
14.3,8,360,245,3.21,3.57,15.84,0,0,3,4
24.4,4,146.7,62,3.69,3.19,20,1,0,4,2
22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4
17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4
16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3
17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3
15.2,8,275.8,180,3.07,3.78,18,0,0,3,3
10.4,8,472,205,2.93,5.25,17.98,0,0,3,4
10.4,8,460,215,3,5.424,17.82,0,0,3,4
14.7,8,440,230,3.23,5.345,17.42,0,0,3,4
32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1
30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2
33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1
21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1
15.5,8,318,150,2.76,3.52,16.87,0,0,3,2
15.2,8,304,150,3.15,3.435,17.3,0,0,3,2
13.3,8,350,245,3.73,3.84,15.41,0,0,3,4
19.2,8,400,175,3.08,3.845,17.05,0,0,3,2
27.3,4,79,66,4.08,1.935,18.9,1,1,4,1
26,4,120.3,91,4.43,2.14,16.7,0,1,5,2
30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2
15.8,8,351,264,4.22,3.17,14.5,0,1,5,4
19.7,6,145,175,3.62,2.77,15.5,0,1,5,6
15,8,301,335,3.54,3.57,14.6,0,1,5,8
21.4,4,121,109,4.11,2.78,18.6,1,1,4,2
## file: shared.py
from pathlib import Path
import pandas as pd
app_dir = Path(__file__).parent
mtcars = pd.read_csv(app_dir / "mtcars.csv")
data_view_rows
express.render.data_frame.data_view_rows()
Reactive calculation of the data frame's user view row numbers.
This value is a wrapper around input.<id>_data_view_rows()
, where <id>
is the id
of the data frame output.
Returns
filter
filter() express.render.data_frame.
Reactive calculation of the data frame's column filters.
Returns
: tuple[
ColumnFilter
, …]-
An array of
col
umn number andvalue
information. If the column type is a number, a tuple of(min, max)
is used forvalue
. If no min (or max) value is set,None
is used in its place. If the column type is a string, the string value is used forvalue
.
input_cell_selection
express.render.data_frame.input_cell_selection()
[Deprecated] Reactive calculation of selected cell information.
Please use ~shiny.express.render.data_frame
's .cell_selection()
method instead.
selection_modes
express.render.data_frame.selection_modes()
Reactive calculation of the data frame's possible selection modes.
Returns
:
SelectionModes
-
The possible selection modes for the data frame.
set_patch_fn
express.render.data_frame.set_patch_fn(fn)
Decorator to set the function that updates a single cell in the data frame.
The default patch function returns the value as is.
Parameters
fn :
PatchFn
|PatchFnSync
-
A function that accepts a kwarg
patch
and returns the processedpatch.value
for the cell.
Examples
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 400
## file: app.py
from shared import mtcars
from shiny import reactive
from shiny.express import render, ui
df = reactive.value(mtcars.iloc[:, range(4)])
with ui.layout_columns(col_widths=[4, 4, 4]):
with ui.card():
with ui.card_header():
ui.markdown(
"""
##### Editable data frame
* Edit the cells!
* Sort the columns!
"""
)
@render.data_frame
def df_original():
return render.DataGrid(
df(),
editable=True,
)
# Convert edited values to the correct data type
@df_original.set_patch_fn
def _(*, patch: render.CellPatch) -> render.CellValue:
if patch["column_index"] in [0, 2]:
return float(patch["value"])
return int(patch["value"])
with ui.card():
with ui.card_header():
ui.markdown(
"""
##### Updated data from the first data frame
* Select the rows!
* Filter and sort the columns!
"""
)
@render.data_frame
def df_edited():
return render.DataGrid(
df_original.data_view(),
selection_mode="rows",
filters=True,
)
with ui.card():
with ui.card_header():
ui.markdown(
"""
##### Selected data from the second data frame
* Sort the columns!
"""
)
@render.data_frame
def df_selected():
return render.DataGrid(
df_edited.data_view(selected=True),
selection_mode="none",
)
## file: mtcars.csv
mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
21,6,160,110,3.9,2.62,16.46,0,1,4,4
21,6,160,110,3.9,2.875,17.02,0,1,4,4
22.8,4,108,93,3.85,2.32,18.61,1,1,4,1
21.4,6,258,110,3.08,3.215,19.44,1,0,3,1
18.7,8,360,175,3.15,3.44,17.02,0,0,3,2
18.1,6,225,105,2.76,3.46,20.22,1,0,3,1
14.3,8,360,245,3.21,3.57,15.84,0,0,3,4
24.4,4,146.7,62,3.69,3.19,20,1,0,4,2
22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4
17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4
16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3
17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3
15.2,8,275.8,180,3.07,3.78,18,0,0,3,3
10.4,8,472,205,2.93,5.25,17.98,0,0,3,4
10.4,8,460,215,3,5.424,17.82,0,0,3,4
14.7,8,440,230,3.23,5.345,17.42,0,0,3,4
32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1
30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2
33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1
21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1
15.5,8,318,150,2.76,3.52,16.87,0,0,3,2
15.2,8,304,150,3.15,3.435,17.3,0,0,3,2
13.3,8,350,245,3.73,3.84,15.41,0,0,3,4
19.2,8,400,175,3.08,3.845,17.05,0,0,3,2
27.3,4,79,66,4.08,1.935,18.9,1,1,4,1
26,4,120.3,91,4.43,2.14,16.7,0,1,5,2
30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2
15.8,8,351,264,4.22,3.17,14.5,0,1,5,4
19.7,6,145,175,3.62,2.77,15.5,0,1,5,6
15,8,301,335,3.54,3.57,14.6,0,1,5,8
21.4,4,121,109,4.11,2.78,18.6,1,1,4,2
## file: shared.py
from pathlib import Path
import pandas as pd
app_dir = Path(__file__).parent
mtcars = pd.read_csv(app_dir / "mtcars.csv")
set_patches_fn
express.render.data_frame.set_patches_fn(fn)
Decorator to set the function that updates a batch of cells in the data frame.
The default patches function calls the async ._patch_fn()
on each input patch and returns the updated patch values.
There are no checks made on the quantity of patches returned. The user can return more, less, or the same number of patches as the input patches. This allows for the app author to own more control over which columns are updated and how they are updated.
Parameters
fn :
PatchesFn
|PatchesFnSync
-
A function that accepts a kwarg
patches
and returns a list of (possibly updated) patches to apply to the data frame.
Examples
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 400
## file: app.py
from __future__ import annotations
from pathlib import Path
import pandas as pd
from shiny import reactive
from shiny.express import render, ui
here = Path(__file__).parent
mtcars_df = reactive.value(pd.read_csv(here / "mtcars.csv").iloc[:, range(4)])
# A copy of the data frame that will store all the edits
edited_df = reactive.value(None)
# Copy mtcars_df to edited_df when mtcars_df changes and on initial load
@reactive.effect
def _sync_mtcars_to_edited_df():
edited_df.set(mtcars_df())
ui.markdown(
"""
#### Instructions:
* Run the app locally so that the edits to the underlying CSV file will persist.
* Edit the cells in the table.
#### Note:
The data frame will not be re-rendered as the result of `df()` has not updated.
Once the `df()` is invalidated, all local edits are forgotten, and the data frame will be re-rendered. However, since the edits were saved to the CSV file, the edits will persist between refreshes (when run locally).
"""
)
with ui.card():
@render.data_frame
def my_data_frame():
return render.DataGrid(
mtcars_df(),
editable=True,
)
# Save the edited values to the data source (ex: the CSV file)
@my_data_frame.set_patches_fn
def _(*, patches: list[render.CellPatch]) -> list[render.CellPatch]:
for patch in patches:
if patch["column_index"] in [0, 2]:
patch["value"] = float(patch["value"])
else:
patch["value"] = int(patch["value"])
# "Save to the database" by writing the edited data to a CSV file
df = edited_df().copy()
for patch in patches:
df.iloc[patch["row_index"], patch["column_index"]] = patch["value"]
edited_df.set(df)
df.to_csv(here / "mtcars.csv", index=False)
print("Saved the edited data to './mtcars.csv'")
return patches
## file: mtcars.csv
mpg,cyl,disp,hp
21.0,6,160.0,110
21.0,6,160.0,110
22.8,4,108.0,93
21.4,6,258.0,110
18.7,8,360.0,175
18.1,6,225.0,105
14.3,8,360.0,245
24.4,4,146.7,62
22.8,4,140.8,95
19.2,6,167.6,123
17.8,6,167.6,123
16.4,8,275.8,180
17.3,8,275.8,180
15.2,8,275.8,180
10.4,8,472.0,205
10.4,8,460.0,215
14.7,8,440.0,230
32.4,4,78.7,66
30.4,4,75.7,52
33.9,4,71.1,65
21.5,4,120.1,97
15.5,8,318.0,150
15.2,8,304.0,150
13.3,8,350.0,245
19.2,8,400.0,175
27.3,4,79.0,66
26.0,4,120.3,91
30.4,4,95.1,113
15.8,8,351.0,264
19.7,6,145.0,175
15.0,8,301.0,335
21.4,4,121.0,109
sort
express.render.data_frame.sort()
Reactive calculation of the data frame's column sorting information.
Returns
: tuple[
ColumnSort
, …]-
An array of
col
umn number and isdesc
ending information.
update_cell_selection
express.render.data_frame.update_cell_selection(selection)
Update the cell selection in the data frame.
Currently only single ("type": "row"
) or multiple ("type": "rows"
) row selection is supported.
If the current data frame selection mode is "none"
and a non-none selection is provided, a warning will be raised and no rows will be selected. If cells are supposes to be selected, the selection mode returned from the render function must (currently) be set to "row"
or "rows"
.
Parameters
selection :
CellSelection
| Literal[‘all’] | None |BrowserCellSelection
-
The cell selection to apply to the data frame. This can be a
CellSelection
object,"all"
to select all cells (if possible), orNone
to clear the selection.
update_cell_value
*, row, col) express.render.data_frame.update_cell_value(value,
Update the value of a cell in the data frame.
Calling this method will set a new entry in .cell_patches()
. It will not reset the user's sorting or filtering of their rendered data frame.
Parameters
value :
CellValue
-
The new value to set the cell to.
row : int
-
The row index of the cell to update.
column :
-
The column index of the cell to update.
update_data
express.render.data_frame.update_data(data)
Update the data frame with new data.
Calling this method will update the .data()
data frame with new data and will remove all .cell_patches()
. It will not reset the user's sorting or filtering of their rendered data frame. Any incompatible sorting or filtering settings will be silently dropped.
Parameters
data :
IntoDataFrameT
-
The new data to render.
update_filter
filter) express.render.data_frame.update_filter(
Update the column filtering in the data frame.
Parameters
filter :
ListOrTuple
[ColumnFilter
] | None-
A list of column filtering information. If
None
, filtering will be removed.
Examples
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 400
## file: app.py
from shared import mtcars
from shiny import reactive
from shiny.express import input, render, ui
data = reactive.value(mtcars.iloc[:, range(4)])
with ui.card():
with ui.layout_column_wrap(fill=False):
ui.input_action_button("btn", "Filter on columns 0, 1, and 3")
ui.input_action_button("reset", "Reset column filters")
@render.data_frame
def df():
return render.DataGrid(data(), filters=True)
@reactive.effect
@reactive.event(input.reset)
async def _():
await df.update_filter(None)
@reactive.effect
@reactive.event(input.btn)
async def _():
await df.update_filter(
[
{"col": 0, "value": [19, 25]},
{"col": 1, "value": [None, 6]},
{"col": 3, "value": [100, None]},
]
)
## file: mtcars.csv
mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
21,6,160,110,3.9,2.62,16.46,0,1,4,4
21,6,160,110,3.9,2.875,17.02,0,1,4,4
22.8,4,108,93,3.85,2.32,18.61,1,1,4,1
21.4,6,258,110,3.08,3.215,19.44,1,0,3,1
18.7,8,360,175,3.15,3.44,17.02,0,0,3,2
18.1,6,225,105,2.76,3.46,20.22,1,0,3,1
14.3,8,360,245,3.21,3.57,15.84,0,0,3,4
24.4,4,146.7,62,3.69,3.19,20,1,0,4,2
22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4
17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4
16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3
17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3
15.2,8,275.8,180,3.07,3.78,18,0,0,3,3
10.4,8,472,205,2.93,5.25,17.98,0,0,3,4
10.4,8,460,215,3,5.424,17.82,0,0,3,4
14.7,8,440,230,3.23,5.345,17.42,0,0,3,4
32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1
30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2
33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1
21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1
15.5,8,318,150,2.76,3.52,16.87,0,0,3,2
15.2,8,304,150,3.15,3.435,17.3,0,0,3,2
13.3,8,350,245,3.73,3.84,15.41,0,0,3,4
19.2,8,400,175,3.08,3.845,17.05,0,0,3,2
27.3,4,79,66,4.08,1.935,18.9,1,1,4,1
26,4,120.3,91,4.43,2.14,16.7,0,1,5,2
30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2
15.8,8,351,264,4.22,3.17,14.5,0,1,5,4
19.7,6,145,175,3.62,2.77,15.5,0,1,5,6
15,8,301,335,3.54,3.57,14.6,0,1,5,8
21.4,4,121,109,4.11,2.78,18.6,1,1,4,2
## file: shared.py
from pathlib import Path
import pandas as pd
app_dir = Path(__file__).parent
mtcars = pd.read_csv(app_dir / "mtcars.csv")
update_sort
express.render.data_frame.update_sort(sort)
Update the column sorting in the data frame.
The sort will be applied in reverse order so that the first value has the highest precedence. This mean ties will go to the second sort column (and so on).
Parameters
Examples
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 400
## file: app.py
from shared import mtcars
from shiny import reactive
from shiny.express import input, render, ui
data = reactive.value(mtcars.iloc[:, range(4)])
with ui.card():
ui.input_action_button("btn", "Sort on columns 1↑ and 3↓")
@render.data_frame
def df():
return render.DataGrid(data())
@reactive.effect
@reactive.event(input.btn)
async def _():
await df.update_sort([{"col": 1, "desc": False}, {"col": 3, "desc": True}])
## file: mtcars.csv
mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
21,6,160,110,3.9,2.62,16.46,0,1,4,4
21,6,160,110,3.9,2.875,17.02,0,1,4,4
22.8,4,108,93,3.85,2.32,18.61,1,1,4,1
21.4,6,258,110,3.08,3.215,19.44,1,0,3,1
18.7,8,360,175,3.15,3.44,17.02,0,0,3,2
18.1,6,225,105,2.76,3.46,20.22,1,0,3,1
14.3,8,360,245,3.21,3.57,15.84,0,0,3,4
24.4,4,146.7,62,3.69,3.19,20,1,0,4,2
22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4
17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4
16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3
17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3
15.2,8,275.8,180,3.07,3.78,18,0,0,3,3
10.4,8,472,205,2.93,5.25,17.98,0,0,3,4
10.4,8,460,215,3,5.424,17.82,0,0,3,4
14.7,8,440,230,3.23,5.345,17.42,0,0,3,4
32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1
30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2
33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1
21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1
15.5,8,318,150,2.76,3.52,16.87,0,0,3,2
15.2,8,304,150,3.15,3.435,17.3,0,0,3,2
13.3,8,350,245,3.73,3.84,15.41,0,0,3,4
19.2,8,400,175,3.08,3.845,17.05,0,0,3,2
27.3,4,79,66,4.08,1.935,18.9,1,1,4,1
26,4,120.3,91,4.43,2.14,16.7,0,1,5,2
30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2
15.8,8,351,264,4.22,3.17,14.5,0,1,5,4
19.7,6,145,175,3.62,2.77,15.5,0,1,5,6
15,8,301,335,3.54,3.57,14.6,0,1,5,8
21.4,4,121,109,4.11,2.78,18.6,1,1,4,2
## file: shared.py
from pathlib import Path
import pandas as pd
app_dir = Path(__file__).parent
mtcars = pd.read_csv(app_dir / "mtcars.csv")