Issue with Clicking Specific Cells in a Streamlit Pivot Table to Display Filtered Data

Hi there,

I want to implement a pivot table from a dataframe using Streamlit, and when a specific cell in the pivot table is clicked, I want to filter the original dataframe based on the column, row, and value of that cell and display the detailed contents. Is there a way to do this? It seems that the click event currently triggers when clicking the entire row instead of a specific cell, causing an error. I’ve been thinking about this for a few days but haven’t found a solution.

This is example data written for testing
I tried using a mix of AgGrid and HTML, but I still got an error because it seems I can’t click on individual cells.
data = {
‘A’: [‘foo’, ‘foo’, ‘bar’, ‘bar’, ‘baz’, ‘baz’],
‘B’: [‘one’, ‘two’, ‘one’, ‘two’, ‘one’, ‘two’],
‘C’: [‘small’, ‘large’, ‘small’, ‘large’, ‘small’, ‘large’],
‘D’: [‘red’, ‘red’, ‘blue’, ‘blue’, ‘green’, ‘green’],
‘E’: [1, 2, 3, 4, 5, 6],
‘F’: [7, 8, 9, 10, 11, 12],
‘G’: [13, 14, 15, 16, 17, 18],
‘H’: [19, 20, 21, 22, 23, 24],
‘I’: [25, 26, 27, 28, 29, 30],
‘J’: [31, 32, 33, 34, 35, 36]
}

Thanks for your help!

What do you mean by detailed contents?

Hi,

This is not what I was looking for by selecting a value from a cell, but with this code I get to select the row to display and then display data from another dataframe relating it to a field and displaying a graph.

        @st.experimental_fragment
        def generate_dataframe_and_event():
            if "df" not in st.session_state:
                st.session_state.df = df_consum

            event = st.dataframe(
                st.session_state.df,
                key="data",
                on_select="rerun",
                selection_mode=["multi-row"],
            )

            selection = event.selection
            if selection:
                selected_rows = selection["rows"]
                selected_cols = selection["columns"]
                selected_df = st.session_state.df.iloc[selected_rows]

                # Display filas:
                st.write("Selected rows:")
                st.write(st.session_state.df.iloc[selected_rows])

                componentes_seleccionados = selected_df['Componente'].unique()
                df_filtrado = df[df['Componente'].isin(componentes_seleccionados)]
                df_filtrado  = df_filtrado .loc[(df_filtrado .iloc[:, 4:] != 0).any(axis=1)]
                st.write(df_filtrado)
            
                # Agrega una nueva columna al dataframe con los valores seleccionados
                df_filtrado_melted = df_filtrado.melt(id_vars=['Componente','Descripcion','Grupo','Familia'], value_vars=['Enero','Febrero','Marzo','Abril','Mayo'], var_name='Mes', value_name='Valor')

                # Crea una lista de los meses en el mismo orden en que aparecen en el dataframe
                meses = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo']

                # Crea el gráfico de líneas utilizando la función st.line_chart
                st.line_chart(df_filtrado_melted[df_filtrado_melted['Mes'].isin(meses)]['Valor'])

                return st.session_state.df
            else:
                return st.session_state.df

            # return event.selection
            
        generate_dataframe_and_event()

I have a detailed code here. I want to retrieve the original rows from the DataFrame before pivoting when I click on a value in the pivot table, but it’s not easy.:sob:

It seems like cell selection is not working with the code below, so an error occurs when calling it.

I sent the code via mobile since I’m outside right now, so it might look a bit messy. Sorry about that. I’ll fix it when I get home.

import streamlit as st
import pandas as pd
from st_aggrid import AgGrid, GridOptionsBuilder, GridUpdateMode, DataReturnMode

Create dataset

data = {
‘A’: [‘foo’, ‘foo’, ‘foo’, ‘bar’, ‘bar’, ‘bar’, ‘baz’, ‘baz’, ‘baz’, ‘qux’, ‘qux’, ‘qux’],
‘B’: [‘one’, ‘two’, ‘three’, ‘one’, ‘two’, ‘three’, ‘one’, ‘two’, ‘three’, ‘one’, ‘two’, ‘three’],
‘C’: [‘small’, ‘large’, ‘small’, ‘large’, ‘small’, ‘large’, ‘small’, ‘large’, ‘small’, ‘large’, ‘small’, ‘large’],
‘D’: [‘red’, ‘blue’, ‘green’, ‘red’, ‘blue’, ‘green’, ‘red’, ‘blue’, ‘green’, ‘red’, ‘blue’, ‘green’],
‘E’: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
‘F’: [13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24],
‘G’: [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36],
‘H’: [37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48],
‘I’: [49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60],
‘J’: [61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72]
}

df = pd.DataFrame(data)

Create pivot table

pivot_table = pd.pivot_table(df, values=‘E’, index=[‘A’, ‘B’], columns=[‘C’, ‘D’], aggfunc=‘sum’)
pivot_table = pivot_table.reset_index() # Reset index of pivot table to convert it back to a regular DataFrame

Create Streamlit app

st.title(‘Streamlit with AgGrid’)

Convert column names of pivot table to strings for use

pivot_table.columns = [f"{col[0]}_{col[1]}" if isinstance(col, tuple) else col for col in pivot_table.columns]

Set grid options using GridOptionsBuilder

gb = GridOptionsBuilder.from_dataframe(pivot_table)
gb.configure_default_column(editable=True)
gb.configure_selection(‘single’) # Set single selection mode
gridOptions = gb.build()

Render AgGrid component

response = AgGrid(
pivot_table,
gridOptions=gridOptions,
update_mode=GridUpdateMode.SELECTION_CHANGED,
data_return_mode=DataReturnMode.AS_INPUT,
allow_unsafe_jscode=True
)

Get selected row data

selected = response.get(‘selected_rows’, )

Output for debugging

st.write(“Selected Rows:”, selected)
st.write(“Pivot Table Columns:”, pivot_table.columns)

if selected and len(selected) > 0:
selected_values = selected[0]
st.write(“Selected row data:”, selected_values)

if isinstance(selected_values, dict):
    a_value = selected_values['A']
    b_value = selected_values['B']
    
    # Extract C and D values from the column name of the selected cell
    c_value = None
    d_value = None
    for col in selected_values:
        if col not in ['A', 'B'] and selected_values[col] is not None:
            c_value, d_value = col.split('_')
            break
    
    if c_value and d_value:
        st.write(f"Filtering with A: {a_value}, B: {b_value}, C: {c_value}, D: {d_value}")
        
        filtered_df = df[
            (df['A'] == a_value) & 
            (df['B'] == b_value) & 
            (df['C'] == c_value) & 
            (df['D'] == d_value)
        ]
        
        st.write("Filtered DataFrame:")
        st.write(filtered_df)
    else:
        st.write("No valid cell selected.")
else:
    st.write("Selected data is not a dictionary.")

else:
st.write(“No row selected or selected data is None”)

I want the row data before pivoting when creating a pivot table from the above DataFrame. However, cell selection is not working, and only the row is being selected, which causes an error.

We can select a cell in the dataframe but it does not return anything.

However, as of streamlit version 1.35.0, depending on what you try to achieve, we can now select a row, or column or both and it returns a meaningful info. This info can be further utilized to achieve our goal.

To activate selection, we will set the ‘on_select’ parameter to either ‘rerun’ or callable.

Example.

import streamlit as st
import pandas as pd


data = {
    'Region': ['North', 'East', 'South', 'West', 'North', 'East'],
    'Product': ['CPU', 'Motherboard', 'HardDisk', 'GPU', 'CPU', 'GPU'],
    'Sales': [140, 120, 80, 600, 150, 620]
}


df = pd.DataFrame(data)

event = st.dataframe(
    df,
    on_select='rerun',  # activate selection
)

selected_info = event['selection']
st.text(selected_info)

image

With the info returned, we can return the whole row data for that selected row or rows.

df = pd.DataFrame(data)

event = st.dataframe(
    df,
    on_select='rerun',  # activate selection
)

selected_info = event['selection']
st.text(selected_info)

st.markdown('**Selected dataframe**')
df_selected = df.loc[selected_info['rows']]
st.write(df_selected)

image

By default the users can select multiple rows. We can control that by the selection_mode parameter.

To limit the user to a single row selection, we will use the ‘single-row’ parameter value.

event = st.dataframe(
    df,
    on_select='rerun',  # activate selection
    selection_mode='single-row'
)

If you want the user to be limited to a single row and column.

event = st.dataframe(
    df,
    on_select='rerun',  # activate selection
    selection_mode=['single-row', 'single-column']
)

To get the cell value at the intersection.

df = pd.DataFrame(data)

event = st.dataframe(
    df,
    on_select='rerun',  # activate selection
    selection_mode=['single-row', 'single-column']
)

selected_info = event['selection']
st.text(selected_info)

st.markdown('**Selected dataframe**')
df_selected = df.loc[selected_info['rows']]
st.write(df_selected)

if len(selected_info['rows']) and len(selected_info['columns']):
    st.markdown('**Cell value at intersection**')
    cell_value = df.loc[selected_info['rows'][0], selected_info['columns'][0]]
    st.text(cell_value)
3 Likes

Thank you so much! You’ve been a great help. However, while index calls work well in a regular DataFrame, they seem to cause errors when I try to use them in a pivot table with a multi-index.

Given the following data, how can I retrieve the RAW value before pivoting when selecting the VALUE ‘F’ with columns C and W, and rows G and P?
/A B C D E/
/C W G P F/

I tried to pivot based on your code, but even after making step-by-step modifications, I can’t figure out where the error is occurring T_T

import pandas as pd
import streamlit as st

# Create the dataframe
data = {
    'A': ['C', 'D', 'A', 'B', 'D', 'A', 'D', 'C', 'D', 'B'],
    'B': ['X', 'Y', 'Z', 'W', 'X', 'Y', 'Z', 'W', 'X', 'Y'],
    'C': ['G', 'H', 'F', 'H', 'G', 'F', 'E', 'G', 'H', 'K'],
    'D': ['M', 'N', 'O', 'P', 'M', 'N', 'O', 'P', 'M', 'N'],
    'E': ['P', 'Y', 'G', 'T', 'K', 'E', 'H', 'F', 'GG', 'EE'],
}
df = pd.DataFrame(data)

# Create the pivot table
pivot_df = df.pivot_table(index=['C', 'D'], columns=['A', 'B'], values='E', aggfunc=lambda x: ' '.join(x))

# Display the pivot table and enable selection
event = st.dataframe(
    pivot_df,
    on_select='rerun',  # Enable selection
    selection_mode=['single-row', 'single-column']  # Single row, single column selection mode
)

# Get selected information
selected_info = event.get('selection', {})
selected_rows = selected_info.get('rows', [])
selected_columns = selected_info.get('columns', [])

st.markdown('**Selected rows and columns in pivot table**')
st.write(selected_info)

# Print selected index values
if selected_rows:
    st.markdown('**Selected row index in pivot table**')
    st.text(selected_rows[0])
if selected_columns:
    st.markdown('**Selected column index in pivot table**')
    st.text(selected_columns[0])

# Step 1: Get the selected row and column indices
if selected_rows and selected_columns:
    try:
        selected_row_index = pivot_df.index[selected_rows[0]]
        selected_column_index = pivot_df.columns.get_loc(selected_columns[0])
        
        st.markdown('**Selected row and column values**')
        st.text(f"Row: {selected_row_index}, Column: {pivot_df.columns[selected_column_index]}")

        # Step 2: Get the cell value at the intersection in the pivot table
        cell_value = pivot_df.iloc[selected_rows[0], selected_column_index]
        st.markdown('**Cell value at intersection in pivot table**')
        st.text(cell_value)
        
        # Step 3: Retrieve the corresponding row from the original dataframe
        original_row = df[
            (df['C'] == selected_row_index[0]) &
            (df['D'] == selected_row_index[1]) &
            (df['A'] == pivot_df.columns[selected_column_index][0]) &
            (df['B'] == pivot_df.columns[selected_column_index][1])
        ]

        st.markdown('**Original DataFrame row**')
        st.write(original_row)
    except Exception as e:
        st.error(f"Error: {e}")
else:
    st.text('No selection made')

Frame before pivot.

image

“when selecting the VALUE ‘F’ with columns C and W”

There is no column “W”.

And there are no rows G and P. You have to mention the row index for the rows.

Just reset the index.

# Create the pivot table
pivot_df_r = df.pivot_table(index=['C', 'D'], columns=['A', 'B'], values='E', aggfunc=lambda x: ' '.join(x))

pivot_df = pivot_df_r.reset_index()
1 Like