render.data_frame
render.data_frame(self, fn)
Decorator for a function that returns a pandas DataFrame
object (or similar) to render as an interactive table or grid. Features fast virtualized scrolling, sorting, filtering, and row selection (single or multiple).
Returns
Type | Description |
---|---|
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 DataFrame object. (Equivalent to shiny.render.DataGrid(df) .) 3. Any object that has a .to_pandas() method (e.g., a Polars data frame or Arrow table). (Equivalent to shiny.render.DataGrid(df.to_pandas()) .) |
Row Selection
When using the row selection feature, you can access the selected rows by using the <data_frame_renderer>.input_cell_selection()
method, where <data_frame_renderer>
is the render function name that corresponds with the id=
used in outout_data_frame
. Internally, this method retrieves the selected cell information from session's input.<id>_cell_selection()
value. The value returned will be None
if the selection mode is "none"
, or a tuple of integers representing the indices of the selected rows if the selection mode is "row"
or "rows"
. If no rows have been selected (while in a non-"none"
row selection mode), an empty tuple will be returned. To filter a pandas data frame down to the selected rows, use <data_frame_renderer>.data_view()
or df.iloc[list(input.<id>_cell_selection()["rows"])]
.
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 a string value.
To access the data viewed by the user, use <data_frame_renderer>.data_view()
. This method will sort, filter, and apply any patches to the data frame as viewed by the user within the browser. This is a shallow copy of the original data frame. It is possible that alterations to data_view
could alter the original data
data frame.
To access the original data, use <data_frame_renderer>.data()
. This is a quick reference to the original data frame (converted to a pandas.DataFrame
) that was returned from the app's render function. If it is mutated in place, it will modify the original data.
Note… if the data frame renderer is re-rendered due to reactivity, then (currently) the user's edits, sorting, and filtering will be lost. We hope to improve upon this in the future.
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
- output_data_frame
- DataGrid and DataTable are the objects you can return from the rendering function to specify options.
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 output_widget, render_widget
from shiny import App, reactive, render, req, 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)
app_ui = ui.page_fillable(
{"class": "p-3"},
ui.markdown(
"**Instructions**: Select one or more countries in the table below to see more information."
),
ui.layout_columns(
ui.card(ui.output_data_frame("summary_data"), height="400px"),
ui.card(output_widget("country_detail_pop"), height="400px"),
ui.card(output_widget("country_detail_percap"), height="400px"),
col_widths=[12, 6, 6],
),
)
def server(input, output, session):
@render.data_frame
def summary_data():
return render.DataGrid(summary_df.round(2), selection_mode="rows")
@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)]
@render_widget
def country_detail_pop():
return px.line(
filtered_df(),
x="year",
y="pop",
color="country",
title="Population Over Time",
)
@render_widget
def country_detail_percap():
return px.line(
filtered_df(),
x="year",
y="gdpPercap",
color="country",
title="GDP per Capita Over Time",
)
app = App(app_ui, server)
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 400
## file: app.py
from shared import mtcars
from shiny import App, reactive, render, ui
app_ui = ui.page_fillable(
ui.layout_columns(
ui.card(
ui.card_header(
ui.markdown(
"""
##### Editable data frame
* Edit the cells!
* Sort the columns!
"""
)
),
ui.output_data_frame("df_original"),
),
ui.card(
ui.card_header(
ui.markdown(
"""
##### Updated data from the first data frame
* Select the rows!
* Filter and sort the columns!
"""
)
),
ui.output_data_frame("df_edited"),
),
ui.card(
ui.card_header(
ui.markdown(
"""
##### Selected data from the second data frame
* Sort the columns!
"""
)
),
ui.output_data_frame("df_selected"),
),
col_widths=[4, 4, 4],
),
)
def server(input, output, session):
df = reactive.value(mtcars.iloc[:, range(4)])
@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"])
@render.data_frame
def df_edited():
return render.DataGrid(
# Reactive value is updated when the user edits the data within `df_original` output
df_original.data_view(),
selection_mode="rows",
filters=True,
)
@render.data_frame
def df_selected():
return render.DataGrid(
# Reactive value is updated when the user selects rows the data within `df_edited` output
df_edited.data_view(selected=True),
selection_mode="rows",
)
app = App(app_ui, server)
## 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")
#| 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 App, reactive, render, ui
app_ui = ui.page_fillable(
{"class": "p-3"},
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).
"""
),
ui.card(
ui.output_data_frame("my_data_frame"),
),
)
here = Path(__file__).parent
def server(input, output, session):
mtcars_df = reactive.value(pd.read_csv(here / "mtcars.csv").iloc[:, range(4)])
@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
for patch in patches:
df = mtcars_df().copy()
df.iloc[patch["row_index"], patch["column_index"]] = patch["value"]
df.to_csv(here / "mtcars.csv", index=False)
print("Saved the edited data to './mtcars.csv'")
return patches
app = App(app_ui, server)
## 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
Attributes
Name | Description |
---|---|
cell_patches | Reactive value of the data frame’s edits provided by the user. |
data | Reactive value of the data frame’s output data. |
input_cell_selection | Reactive value of selected cell information. |
selection_modes | Reactive value of the data frame’s possible selection modes. |
Methods
Name | Description |
---|---|
data_view | Reactive function that retrieves the data how it is viewed within the browser. |
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. |
update_cell_selection | Update the cell selection in the data frame. |
data_view
render.data_frame.data_view(selected=False)
Reactive function that retrieves the data how it is viewed within the browser.
This function will sort, filter, and apply any patches to the data frame as viewed by the user within the browser.
This is a shallow copy of the original data frame. It is possible that alterations to data_view
could alter the original data
data frame. Please be cautious when using this value directly.
Parameters
selected: bool = False
-
If
True
, subset the viewed data to the selected area. Defaults toFalse
.
Returns
Type | Description |
---|---|
pd .DataFrame |
A view of the data frame as seen in the browser. Even if the rendered data value was not pd.DataFrame , this method currently returns the converted pd.DataFrame . |
See Also
- [
pandas.DataFrame.copy
API documentation]h(ttps://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.copy.html)
set_patch_fn
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.
set_patches_fn
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.
update_cell_selection
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-
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.