Figure Friday 2025 - week 21

join the Figure Friday session on May 30, at noon Eastern Time, to showcase your creation and receive feedback from the community.

Gas, liquid, solid… Where do carbon emissions come from?

Answer this question and many others by using Plotly and Dash on the Global CO2 emissions dataset. If you prefer to analyze emissions per country, see the nation.1751_2021.xlsx file on the second row of the table.

Things to consider:

  • what can you improve in the app or sample figure below (area plot)?
  • would you like to tell a different data story using a different graph?
  • can you create a different Dash app?

Sample figure:

Code for sample figure:
from dash import Dash, dcc
import dash_ag_grid as dag
import plotly.express as px
import pandas as pd


df = pd.read_csv("https://raw.githubusercontent.com/plotly/Figure-Friday/refs/heads/main/2025/week-21/global.1751_2021.csv")
# rename the columns to 'Gas', 'Liquid', 'Solid'
df.rename(columns={'Carbon emissions from solid fuel consumption': 'Solid', 'Carbon emissions from liquid fuel consumption': 'Liquid', 'Carbon emissions from gas fuel consumption': 'Gas'}, inplace=True)

fig = px.area(df, x='Year', y=['Solid', 'Liquid', 'Gas'], title='Global CO2 Emissions')

grid = dag.AgGrid(
    rowData=df.to_dict("records"),
    columnDefs=[{"field": i, 'filter': True, 'sortable': True} for i in df.columns],
    dashGridOptions={"pagination": True},
    columnSize="sizeToFit"
)

app = Dash()
app.layout = [
    grid,
    dcc.Graph(figure=fig)
]


if __name__ == "__main__":
    app.run(debug=False)

Participation Instructions:

  • Create - use the weekly data set to build your own Plotly visualization or Dash app. Or, enhance the sample figure provided in this post, using Plotly or Dash.
  • Submit - post your creation to LinkedIn or Twitter with the hashtags #FigureFriday and #plotly by midnight Thursday, your time zone. Please also submit your visualization as a new post in this thread.
  • Celebrate - join the Figure Friday sessions to showcase your creation and receive feedback from the community.

:point_right: If you prefer to collaborate with others on Discord, join the Plotly Discord channel.

Data Source:

Thank you to the IREEE and to community member, Matt H, for the data.

4 Likes

Hi everyone!
I’m excited to share an interactive CO₂ Emissions Dashboard. It lets you explore global and nation-level emissions from 1751–2021, with features like:

  • Time series of total and per-capita emissions
  • Area charts by emission source (solid, liquid, gas, cement, flaring)
  • Correlation heatmap of key metrics
  • Top 10 bar charts for countries or recent years
  • AI-powered insights (optional) via Google Gemini

Feel free to fork or run it yourself:
:link: Kaggle Notebook: CO₂ Emissions Dashboard | Kaggle
:link: GitHub Repo: GitHub - Feanor1992/CO-Emissions-Dashboard

Looking forward to your feedback and visualizations!

7 Likes

This Dash application is a CO₂ Emissions Dashboard built using Plotly, Dash, and Dash Bootstrap Components. It visualizes global and national carbon dioxide emissions from fossil fuels and cement production.

:wrench: Key Features & Structure:

:bar_chart: Data Source & Preprocessing:

  • Reads emission data from nation.xlsx.
  • Columns are renamed for clarity and ease of use.
  • Main emission types considered:
    • Solid, Liquid, Gas Fuel Emissions
    • Cement Production
    • Gas Flaring

:control_knobs: User Controls:

  • Emission Source Dropdown: Choose specific sources or view all.
  • Year Slider: Select a year between 1900 and the latest in the dataset.

:compass: Tabbed Layout:

  1. Trends & Source Share
  • Line Chart: Global emission trends over time by source.
  • Pie Chart: Share of each emission source for the selected year.
  1. Map & Top 10
  • Choropleth Map: Emissions by country for the selected year.
  • Bar Chart: Top 10 emitting countries for the selected year and source.

:chart_increasing: Callback Function (update_charts)

Updates all four visualizations based on user input (year and source):

  • Line Chart: Stacked area plot showing emission trends.
  • Pie Chart: Shows source distribution as a donut chart.
  • Bar Chart: Top 10 countries with highest emissions.
  • Choropleth Map: Geographical distribution of emissions.

:lipstick: Styling & Theming:

  • Uses dbc.themes.LUX for a modern aesthetic.
  • Cards, headers, and charts styled with white backgrounds and subtle shadows.

:memo: Footer:

Mentions the data source: CDIAC / Appalachian State University.


I’ll send the code a little later, it’s not completely finished yet.
I don’t like my rangefilter at the moment, but I haven’t been able to come up with a better replacement yet, so I’ll try something else. Maybe I will use more css.

8 Likes

Hello everyone! This week, I used one of my passions, gaming, to build this web app: the “Carbon Emission AI Challenge.” This application lets you explore historical carbon emissions, and once you’re familiar with them, it challenges your skills to predict the future of these emissions against artificial intelligence!

Here’s a summary of what you can do:

  • Explore historical data: You’ll be able to see graphs of how carbon emissions have changed over the years, broken down by different sources like solid fuel, liquid fuel, gas, cement production, and gas flaring. This helps you understand past trends.
  • Make your own prediction: This is the heart of the application. You can choose a future year and estimate total carbon emissions, as well as those for each category (solid fuel, liquid fuel, etc.).
  • Compete against AI: Once you enter your predictions, the program compares them with those generated by different artificial intelligence models (Linear Regression, Polynomial, Random Forest).
  • Get your score: You’ll receive an accuracy score based on how close you were to the AI model’s prediction, especially for “Total Emissions.” The application will tell you if your prediction was “Excellent,” “Good,” or if it “Needs improvement.”
  • History and winning streak: The application keeps a record of all your predictions and scores, and shows you your best score and if you have a streak of successful predictions.
  • Detailed model comparison: You’ll be able to see a graph that compares your prediction with the predictions of all AI models for a specific category, showing you who was closer.

The application has many areas for improvement, and I hope you can take a look and give me your comments/suggestions.

Any questions or doubts are highly appreciated. Remember to put your skills to the test!

In a separate message I´ll send the code



7 Likes

pretty cool graphs, @feanor_92 .
What is the line in the left graph telling us, specifically from around the 1970’s to present time? Why is the line flat?

2 Likes

nice one, @Ester . What about the Slider don’t you like? Would it be better as a range slider with two handles so you can also move the past dates?

One thing that would be cool to see is the CO2 emissions standardized by population size. It looks like China and US have the top two highest emissions, but compared to their population size (CO2 per capita/person), are they still the highest?

1 Like

awesome dash app, @Avacsiglo21 .
One quick thing I would recommend is to make the dropdown options’ font color black so it’s easier to see them.

The left side cards are very helpful. I would consider updating the text inside each orange button to use the titles instead of the generic go to {n}. For example,

  • button 1: View Historical Trends
  • button 2: Make Your Prediction
  • button 3: Check Accuracy
    etc.

The prediction part is a lot of fun :slight_smile:
One minor recommendation would be to make a button appear after I make the prediction that says: Check Accuracy. Which would take you to the Results and Analysis page.

I really enjoyed playing around with your dash app, @Avacsiglo21

2 Likes

Hi Adams,

Thanks a lot for all your recomendations, make a lot of sense. By the way, I could not send the code is about 750 lines code and exceed the characters allowed.

2 Likes

no worries, @Avacsiglo21 . We can get the code directly from Py.Cafe.

2 Likes

Thank you @adamschroeder the recommendations, I put a radio-button on the page.

2 Likes

Cool @Avacsiglo21, I hope the models are right!

Two things I do not get,

1.I do not manage to get to multi-model comparison. It says I have to enter a prediction, I enter a prediction and
click on 4, which says I have to enter a prediction.

  1. The result on the image, see red
1 Like

Hi Marianne, thanks for your question!

I see. Currently, to refresh and view the comparison chart after making a prediction, you might need to perform a small trick: first, select a different emission type in the dropdown menu within the ‘Multi Model Comparison’ section, and then switch it back to ‘Total Emissions’ (or your desired category). This action will prompt the chart to update and display the comparison. In this case you just make predictions just for “Total Emissions”.

1 Like


One down :stuck_out_tongue_winking_eye:

1 Like

I couldn’t resist but there is a light/dark mode button in the app. Somehow things look always more true and stylish in darkmode.

Too many ideas, no time left, I’ve stripped a lot and what is left is a sankey generator and a thought I had, this is good for your topological knowledge, which I will not share, maybe to sharp a joke.

You can select a few regions in Europe which have had major changes in terms of war/split ups and one reunion since 1950.
There is no logic with the subject of this figure friday I guess, but all metrics can be selected.

4 Likes

Hi Marieanne,

Really interesting the way you built this dashboard, I like it.

2 Likes

I apologize; I never fully answered your second question. The Random Forest (RF) prediction you highlighted in red clearly demonstrates that this algorithm is not suitable for extrapolating data. This is also because the limited dataset size can cause future predictions to flatten, which is precisely what you observed. Excellent observation, Marianne! Please, if you notice anything else, don’t hesitate to point it out.

3 Likes

Thank you for your answer, I was still curious.

2 Likes

I made a dashboard with 6 major components. Two plots display data on the left, and incremental change on the right. A radio button groups the data by decade or year, with 2nd radio button for plotting with px.line or px.area. Group by decade has the sums for each year of that decade, with 2020s excluded for only having 2 years in data set.

The middle section has a px.scatter correlation graph on the left, and correlation statistics in dbc cards on the right. Radio buttons select the X axis and Y axis parameters. When same parameter is used on both axes, you can see best case, ideal correlation stats. A best fit line was added as a go.Scatter trace.


Bottom has a dash-ag table and a card describing each parameter. I renamed all columns and kept the original long descriptive text for the parameter descriptions on the right, and as dash-ag tooltips.

Here is the code:

import polars as pl
import plotly.express as px
import plotly.graph_objects as go
from dash import Dash, dcc, html, Input, Output
import dash_bootstrap_components as dbc
import dash_ag_grid as dag
import dash_mantine_components as dmc
import dash
from scipy import stats
dash._dash_renderer._set_react_version('18.2.0')

#----- GLOBALS -----------------------------------------------------------------

data_src ='Data Source:  https://rieee.appstate.edu/projects-programs/cdiac/'

df_col_defs  = [c for c in pl.scan_csv('global.1751_2021.csv').collect_schema()]
short_col_names = [
    'YEAR', 'FOSSIL', 'SOLID', 'LIQUID', 'GAS', 
    'CEMENT', 'FLARING', 'PER_CAP','DECADE']
dict_cols = dict(zip(df_col_defs, short_col_names))
dict_cols_reversed = dict(zip(short_col_names, df_col_defs))
dag_columns = [
    'DECADE', 'YEAR', 'CEMENT', 'FLARING', 'FOSSIL', 
    'GAS', 'LIQUID', 'SOLID', 'PER_CAP', ]

style_horiz_line = {'border': 'none', 'height': '4px', 
    'background': 'linear-gradient(to right, #007bff, #ff7b00)', 
    'margin': '10px,', 'fontsize': 32}

style_h2 = {'text-align': 'center', 'font-size': '32px'}
style_h3 = {
    'text-align': 'center', 'font-size': '16px', 'font-weight': 'normal'}

#----- DASHBOARD COMPONENTS ----------------------------------------------------
grid = dag.AgGrid(
    rowData=[],
    columnDefs=[
        {"field": i, 'filter': True, 'sortable': True,
         'tooltipField': i, 'headerTooltip': dict_cols_reversed.get(i) } 
        for i in dag_columns
    ],
    dashGridOptions={"pagination": True},
    columnSize="responsiveSizeToFit",
    id='dash_ag_table',
)

definition_card = dbc.Card([
    dbc.CardBody([
        dbc.ListGroup(
            [
            dbc.ListGroupItem(f'CEMENT: {dict_cols_reversed['CEMENT']}'),
            dbc.ListGroupItem(f'FLARING: {dict_cols_reversed['FLARING']}'),
            dbc.ListGroupItem(f'FOSSIL: {dict_cols_reversed['FOSSIL']}'),
            dbc.ListGroupItem(f'GAS: {dict_cols_reversed['GAS']}'),
            dbc.ListGroupItem(f'LIQUID: {dict_cols_reversed['LIQUID']}'),
            dbc.ListGroupItem(f'SOLID: {dict_cols_reversed['SOLID']}'),
            dbc.ListGroupItem(f'PER_CAP: {dict_cols_reversed['PER_CAP']}'),
            ],
        )
    ]),
])

regression_stats_card = (
    dbc.Card([
        html.H3('Stats', className='card-header',),
        dbc.ListGroup([
                dbc.ListGroupItem('Slope'),
                dbc.ListGroupItem('Slope', id='lg_slope_val'),
            ],
            horizontal=True,
            flush=True,
        ),
        dbc.ListGroup([
                dbc.ListGroupItem('Intercept'),
                dbc.ListGroupItem('Intercept', id='lg_intcpt_val'),
            ],
            horizontal=True,
            flush=True,
        ),
        dbc.ListGroup([
                dbc.ListGroupItem('Correlation'),
                dbc.ListGroupItem('Correlation', id='lg_corr_val'),
            ],
            horizontal=True,
            flush=True,
        ),
        dbc.ListGroup([
                dbc.ListGroupItem('Standard Error'),
                dbc.ListGroupItem('Standard Error', id='lg_stderr'),
            ],
            horizontal=True,
            flush=True,
        ),
        dbc.ListGroup([
                dbc.ListGroupItem('Intercept Std Err'),
                dbc.ListGroupItem('Intercept Std Err', id='lg_i_stderr'),
            ],
            horizontal=True,
            flush=True,
        ),
    ]),
)

#----- READ & CLEAN DATASET, STORE AS 4 DATAFRAMES -----------------------------
df_year = (
    pl.scan_csv('global.1751_2021.csv')  # lazyframe
    .rename(dict_cols)
    .with_columns(
        pl.col('PER_CAP').cast(pl.Float32),
        pl.col('YEAR').cast(pl.String)
    )
    .with_columns(
        DECADE = pl.col('YEAR').str.slice(0,3) +  pl.lit("0's")
    )
    .collect() # dataframe
)

df_year_diff = (
    df_year
    .lazy()    # lazyframe
    .with_columns(
        pl.col(
            'FOSSIL', 'SOLID', 'LIQUID', 'GAS', 
            'CEMENT', 'FLARING', 'PER_CAP'
        ).diff()
    )
    .collect()   # dataframe
)

df_decade = (
    df_year
    .lazy()   # lazyframe
    .with_columns(DECADE = pl.col('YEAR').str.slice(0,3) +  pl.lit("0's"))
    .filter(pl.col('DECADE') != "2020's")  # only data for 2 years in the 2020s
    .group_by('DECADE').agg(
        pl.col('FOSSIL').sum(),
        pl.col('SOLID').sum(),
        pl.col('LIQUID').sum(),
        pl.col('GAS').sum(),
        pl.col('CEMENT').sum(),
        pl.col('FLARING').sum(),
        pl.col('PER_CAP').sum(),
    )
    .with_columns(YEAR = pl.lit('NA'))
    .sort('DECADE')
    .collect()    # dataframe
)

df_decade_diff = (
    df_decade
    .lazy()  # lazyframe
    .with_columns(
        pl.col(
            'FOSSIL', 'SOLID', 'LIQUID', 'GAS', 
            'CEMENT', 'FLARING', 'PER_CAP'
        ).diff()
    )
    .collect()   # dataframe
)

#----- CALLBACK FUNCTIONS ------------------------------------------------------

def get_corr_stat(stat, corr_x, corr_y, group_by):
    df_plot = df_year.drop_nulls([corr_x, corr_y])
    if group_by == 'DECADE':
        df_plot = df_decade.drop_nulls([corr_x, corr_y])
    res = stats.linregress(df_plot[corr_x], df_plot[corr_y])
    if stat == 'SLOPE':
        val = res.slope
    elif stat == 'INTERCEPT':  
        val = res.intercept
    elif stat == 'CORR':  
        val = res.rvalue**2
    elif stat == 'STDERR':  
        val = res.stderr
    elif stat == 'I_STDERR': 
        val = res.intercept_stderr
    else:
        print('illegal value, unrecognized stat')
        val = -999999
    return val

def get_data_plot(data_type, group_by, graph_type):
    if (data_type, group_by) == ('DATA', 'YEAR'):
        df_plot = df_year
        graph_title = f'<b>{graph_type} PLOT BY {group_by}</b>'
        y_axis_label = 'TOTAL BY YEAR'
    if (data_type, group_by) == ('DIFF', 'YEAR'):
        df_plot = df_year_diff
        graph_title = f'<b>{graph_type} PLOT INCREMENTAL, {group_by} / {group_by}</b>'
        y_axis_label = 'YEAR/YEAR'
    if (data_type, group_by) == ('DATA', 'DECADE'):
        df_plot = df_decade
        graph_title = f'<b>{graph_type} PLOT BY {group_by}</b>'
        y_axis_label = 'TOTAL BY DECADE'
    if (data_type, group_by) == ('DIFF', 'DECADE'):
        df_plot = df_decade_diff
        graph_title = f'<b>{graph_type} PLOT INCREMENTAL, {group_by} / {group_by}</b>'
        y_axis_label = 'DECACE/DECADE'
    if graph_type == 'LINE':
        fig=px.line(
            df_plot,
            group_by,
            short_col_names[1:-1],
        )
    if graph_type == 'AREA':
        fig=px.area(
            df_plot,
            group_by,
            short_col_names[1:-1],
        )
    fig.update_layout(
        template='simple_white',
        title_text=graph_title,
        yaxis=dict(title=dict(text=y_axis_label))
    )
    if group_by == 'YEAR':
        int_years = sorted(
            [int(x) for x in df_plot['YEAR'].unique() if int(x)%10 ==0]
        )
        fig.update_xaxes(
            tickangle=90,
            tickmode = 'array',
            tickvals = [x for x in int_years if x%10 ==0],
            ticktext= [str(x) for x in int_years if x%10 ==0]
        )
    return fig

def get_corr_plot(corr_x, corr_y, group_by):
    if group_by == 'YEAR':
        df_plot = df_year.drop_nulls([corr_x, corr_y])
    if group_by == 'DECADE':
        df_plot = df_decade.drop_nulls([corr_x, corr_y])
    regression_params = stats.linregress(df_plot[corr_x], df_plot[corr_y])
    x_min = df_plot[corr_x].min()
    x_max = df_plot[corr_x].max()
    # find values for y at x_min and x_max using y = mx + b
    y_at_x_min = (regression_params.slope * x_min) + regression_params.intercept
    y_at_x_max = (regression_params.slope * x_max) + regression_params.intercept
    fig=px.scatter(
        df_plot,
        x=corr_x,
        y=corr_y,
        color='DECADE',   
    )
    fig.add_trace(
        go.Scatter(
            x=[x_min, x_max], 
            y=[y_at_x_min, y_at_x_max], 
            mode='lines', 
            line=dict(dash='longdashdot',width=3, color='lightgray'),
        )
    )

    fig.update_layout(
        template='simple_white',
        title_text=f'<b>CORRELATION PLOT BY {group_by}</b>',
        xaxis=dict(title=dict(text=f'{corr_x}')),
        yaxis=dict(title=dict(text=f'{corr_y}')),
        showlegend=False,
    )
    return fig

def get_table(group_by):
    if group_by == 'YEAR':
        df = df_year.select(dag_columns)
    if group_by == 'DECADE':
        df = (
            df_decade
            .select(dag_columns)
        )
    return df.to_dicts()

#----- DASH APPLICATION STRUCTURE-----------------------------------------------
app = Dash(external_stylesheets=[dbc.themes.LITERA])
app.layout =  dmc.MantineProvider(
    [
        html.Hr(style=style_horiz_line),
        html.H2('CO2 EMISSIONS DATA', style=style_h2),
        html.H3(data_src, style=style_h3),
        html.Hr(style=style_horiz_line),
        dbc.Row([ # cols 2 & 3 for Group by, 5 & 6 for Plot type
            dbc.Col(html.Div('Group by:'), width={'size': 2, 'offset': 1}),
            dbc.Col(html.Div('Plot type:'), width={'size': 2, 'offset': 1}),
        ]),
        dbc.Row([       
            dbc.Col([
                dcc.RadioItems(
                    ['DECADE', 'YEAR'], 'YEAR', inline=False,
                    labelStyle={'margin-right': '30px',},
                    id='group_by_radio', 
                ),],
                width={'size': 2, 'offset': 1} # cols 2 & 3
            ), 
            dbc.Col([dcc.RadioItems(
                    ['AREA', 'LINE'], 'AREA',  inline=False,
                    labelStyle={'margin-right': '30px',},
                    id='graph_type_radio', 
                ),],
                width={'size': 2, 'offset': 1}), # column 5 to 6
        ]),
        html.Div(),
        dbc.Row(
            [
                dbc.Col(dcc.Graph(id='graph_plot'), width=6),
                dbc.Col(dcc.Graph(id='graph_diff'), width=6),
            ]
        ),
        html.Div(),
        html.Hr(style=style_horiz_line),
        html.H2('Correlations', style=style_h2),
        html.H3('Pearson values from scipy.stats', style=style_h3),
        html.Hr(style=style_horiz_line),
        dbc.Row([
            dbc.Col(html.Div('X-Axis:'), width={'size': 1, 'offset': 1}),
            dbc.Col([
                dcc.RadioItems(
                    sorted(short_col_names[1:-1]), 
                    sorted(short_col_names[1:-1])[0], 
                    inline=True,
                    labelStyle={'margin-left': '30px','margin-right': '30px'},
                    id='corr_x_radio', 
                ),
            ],
            width={'size': 10, 'offset': 0}),
        ]),
        html.Div(),
        dbc.Row([       
            dbc.Col(html.Div('Y-Axis:'), width={'size': 1, 'offset': 1}),
            dbc.Col([
                    dcc.RadioItems(
                        sorted(short_col_names[1:-1]), 
                        sorted(short_col_names[1:-1])[2], 
                        inline=True,
                        labelStyle={'margin-left': '30px','margin-right': '30px'},
                        id='corr_y_radio',  
                    ),
                ],
                width={'size': 10, 'offset': 0}
            ),
        ]),
        html.Div(),
        html.Div("", className="w-25 p-3 bg-transparent border-0"),
        dbc.Row([
            dbc.Col(dcc.Graph(id='graph_corr')),
            dbc.Col(regression_stats_card)
        ]),
        html.Div("", className="w-25 p-3 bg-transparent border-0"),
        html.Div(),
        html.Hr(style=style_horiz_line),
        html.H2('Data and Definitions',id='data_and_defs',style=style_h2),
        html.Hr(style=style_horiz_line),
        dbc.Row([
            dbc.Col([grid], width=8),
            dbc.Col(definition_card, width=4),
        ])
    ]
)

@app.callback(
    Output('graph_plot', 'figure'),
    Output('graph_diff', 'figure'),
    Output('graph_corr','figure'),
    Output('dash_ag_table', 'rowData'),
    Output('lg_slope_val','children'),
    Output('lg_intcpt_val','children'),
    Output('lg_corr_val','children'),
    Output('lg_stderr','children'),
    Output('lg_i_stderr','children'),
    Output('data_and_defs','children'),

    Input('group_by_radio', 'value'),
    Input('graph_type_radio', 'value'),
    Input('corr_x_radio', 'value'),
    Input('corr_y_radio', 'value'),
)
def update_dashboard(group_by, graph_type, corr_x, corr_y):
    data_defs_title = 'Data and Definitions'
    if group_by == 'DECADE':
        data_defs_title = 'Data (grouped by decade) and Definitions'
    return (
        get_data_plot('DATA', group_by, graph_type),
        get_data_plot('DIFF', group_by, graph_type),
        get_corr_plot(corr_x, corr_y, group_by),
        get_table(group_by),
        f'{get_corr_stat('SLOPE', corr_x, corr_y, group_by):.3f}',
        f'{get_corr_stat('INTERCEPT',  corr_x, corr_y, group_by):.3f}',
        f'{get_corr_stat('CORR',corr_x,  corr_y, group_by):.3f}',
        f'{get_corr_stat('STDERR', corr_x, corr_y, group_by):.3f}',
        f'{get_corr_stat('I_STDERR', corr_x, corr_y, group_by):.3f}',
        data_defs_title
    )
if __name__ == '__main__':
    app.run_server(debug=True)
6 Likes

Hi,

My simple two chart analysis. This week I experimented with a slight bit of design (background color, font, alignment, etc.) so tiny steps. Also on the second one I added a radio button to compare carbon emissions either ‘per capita’ or ‘total emissions’, limited to the top 10 countries this type of chart gets overwhelmed with information fast - which probably means I should have picked a different type.

Also, I created a py.cafe account and added it there but for some reason it seems Pandas is not loading so get an error. If any one can let me know how to fix this I would be grateful.

first the error message, then the pics of the analysis.

Error in dash app:
Traceback (most recent call last):

  File "/lib/python3.12/jc/dash.py", line 110, in load_app
    import app as main_module

  File "/home/pyodide/project-files/app.py", line 3, in <module>
    import pandas as pd

ModuleNotFoundError: No module named 'pandas'

br,
Mike

3 Likes

Nice work, @mike_finko . Don’t forget to go into the requirements.txt file and add pandas to it.