Courses/Streamlit: 20 Real Apps/Module 4: Data Apps
Module 4 · Lesson 330 minIntermediate

Project 11: CSV Data Explorer

What you'll build
Build a zero-configuration profiling tool that works on any CSV you feed it.

Introduction

Every data project starts the same way: open a CSV, check its shape, peek at rows, check for nulls, eyeball distributions. This app automates that ritual into an instant explorer — upload any CSV and get a full profile, interactive filtering, sorting, and charts with zero configuration. It is the tool you will actually use weekly, built from the exact patterns in the Pandas dashboard tutorial.

The design philosophy is infer everything: column types, filter controls, and chart suggestions all derive from the data itself, so the app works on any file you throw at it.

Features

  • Instant profile — row/column counts, dtypes, memory usage, null counts.
  • Numeric summaries — mean, median, min/max, and quartiles per column.
  • Smart filters — sliders for numbers, selectboxes for categories.
  • Sortable table — full dataframe with column sorting.
  • Quick charts — histogram for any numeric column, bar for any category.
  • Prerequisites

  • Python 3.8+ — from python.org.
  • Dependencies:
  • code
    pip install streamlit pandas

    Step 1: Create the Script

    Save as csv_explorer.py:

    code
    import streamlit as st
    import pandas as pd
    
    st.set_page_config(page_title="CSV Explorer", page_icon="🗂️", layout="wide")
    st.title("🗂️ CSV Data Explorer")
    
    uploaded = st.file_uploader("Upload a CSV", type=["csv"])
    
    if uploaded:
        df = pd.read_csv(uploaded)
    
        c1, c2, c3 = st.columns(3)
        c1.metric("Rows", f"{len(df):,}")
        c2.metric("Columns", len(df.columns))
        c3.metric("Null cells", int(df.isna().sum().sum()))
    
        with st.expander("📋 Column profile"):
            profile = pd.DataFrame({
                "dtype": df.dtypes.astype(str),
                "nulls": df.isna().sum(),
                "unique": df.nunique(),
            })
            st.dataframe(profile, use_container_width=True)
    
        # Sidebar filters inferred from column types
        st.sidebar.header("Filters")
        filtered = df.copy()
        for col in df.columns:
            if pd.api.types.is_numeric_dtype(df[col]) and df[col].nunique() > 10:
                lo, hi = float(df[col].min()), float(df[col].max())
                if lo < hi:
                    rng = st.sidebar.slider(col, lo, hi, (lo, hi), key=f"f_{col}")
                    filtered = filtered[filtered[col].between(*rng)]
            elif df[col].nunique() <= 25:
                choices = st.sidebar.multiselect(col, sorted(df[col].dropna().unique()), key=f"f_{col}")
                if choices:
                    filtered = filtered[filtered[col].isin(choices)]
    
        st.subheader(f"{len(filtered):,} rows match your filters")
        st.dataframe(filtered, use_container_width=True, height=350)
    
        st.divider()
        col_chart, type_hint = st.columns([0.3, 0.7])
        chart_col = col_chart.selectbox("Chart a column", df.columns)
        if pd.api.types.is_numeric_dtype(df[chart_col]):
            st.bar_chart(filtered[chart_col].value_counts().head(20))
            st.caption("Distribution (top 20 values)")
        else:
            st.bar_chart(filtered[chart_col].value_counts().head(15))
            st.caption("Count per category (top 15)")
    else:
        st.info("Upload a CSV file to start exploring.")

    Step 2: Run the App

    code
    streamlit run csv_explorer.py

    Try it on any CSV you have handy — exports from the expense tracker or habit tracker work great.

    How It Works

    The app is built around one Pandas concept: type introspection. pd.api.types.is_numeric_dtype decides which UI each column gets — sliders for numbers, multiselects for low-cardinality categories. The nunique() > 10 guard prevents absurd sliders on ID-like numeric columns, and nunique() <= 25 keeps category pickers sane on free-text columns.

    Filters compose by construction: each one narrows the filtered copy in sequence, so combining "amount between 10–50" and "category is Food" just works. Because every widget has a key derived from the column name, switching files resets cleanly.

    The chart section applies the same inference: numeric columns get value distributions, categorical columns get counts. value_counts().head(20) is the workhorse — top-N only, so a column with 10,000 unique values doesn't produce 10,000 bars.

    Common Errors & Fixes

  • `UnicodeDecodeError` on upload — the file isn't UTF-8 (common with Excel exports); retry with pd.read_csv(uploaded, encoding="latin-1") or detect with charset-normalizer.
  • Sliders on every column make the sidebar huge — raise the numeric uniqueness threshold or collapse filters into an expander.
  • Dates treated as strings — pass parse_dates="infer" or list date columns explicitly; ISO-format dates usually parse automatically.
  • Memory errors on huge files — read only what you need: pd.read_csv(uploaded, usecols=[...]) or sample with nrows=50000 for exploration.
  • Key Concepts

  • Type inference drives UI — the schema decides the controls.
  • Composable filters — sequential narrowing on a copy.
  • `value_counts` + head(N) — bounded aggregations for readable charts.
  • Widget keys from data — automatic state isolation per column.
  • What to Try Next

  • Add a correlation heatmap of numeric columns with df.corr() and st.pyplot.
  • Add a download button exporting the filtered dataframe — the PDF merger's BytesIO pattern with to_csv.
  • Support Excel files with pd.read_excel — see the Excel report generator for the writing side.
  • Add null-handling options — drop, fill with median, or flag.
  • FAQ

    How large a CSV can this handle?

    Comfortably up to a few hundred thousand rows on a laptop — Streamlit serializes the dataframe to the browser, which becomes the bottleneck before Pandas does. Sample beyond that.

    Can I save my filters?

    They persist while the session lives (widget state). For reusable views, store filter settings in a JSON file keyed by filename.

    Why does my ID column get a slider?

    It's numeric with many unique values — exactly what the threshold can't distinguish from a measure. Add a name-based exclusion (if "id" in col) for your conventions.

    Adapted from: CSV Data Explorer using Python, Pandas, and Streamlit

    Checkpoint
    Uploading a file instantly shows its profile, smart filters, and charts.
    What you learned
    • Type inference driving the UI
    • Composable sidebar filters
    • value_counts for bounded charts