SimpleStats / src /streamlit_app.py
enacimie's picture
Update src/streamlit_app.py
094716a verified
Raw
History Blame Contribute Delete
8.63 kB
import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px
from scipy import stats
import io
# Metadata
AUTHOR = "Eduardo Nacimiento García"
EMAIL = "enacimie@ull.edu.es"
LICENSE = "Apache 2.0"
# Page config
st.set_page_config(
page_title="SimpleStats",
page_icon="📊",
layout="wide",
initial_sidebar_state="expanded",
)
# Title and credits
st.title("📊 SimpleStats")
st.markdown(f"**Author:** {AUTHOR} | **Email:** {EMAIL} | **License:** {LICENSE}")
st.write("""
Upload a CSV file or try the built-in demo dataset to perform statistical analysis: summary, charts, hypothesis tests, and more.
""")
# Generate demo dataset
@st.cache_data
def create_demo_data():
np.random.seed(42)
n = 200
data = {
"Age": np.random.normal(35, 12, n).astype(int),
"Income": np.random.normal(45000, 15000, n),
"Satisfaction": np.random.randint(1, 11, n), # scale 1-10
"Group": np.random.choice(["A", "B", "C"], n),
"Gender": np.random.choice(["M", "F"], n, p=[0.6, 0.4]),
"Purchase": np.random.choice([0, 1], n, p=[0.7, 0.3])
}
df = pd.DataFrame(data)
# Introduce some nulls for demo
df.loc[np.random.choice(df.index, 10), "Income"] = np.nan
df.loc[np.random.choice(df.index, 5), "Age"] = np.nan
return df
demo_df = create_demo_data()
# Button to load demo data
if st.button("🧪 Load Demo Dataset"):
st.session_state['uploaded_file'] = demo_df.to_csv(index=False).encode('utf-8')
st.session_state['file_name'] = "demo_data.csv"
st.success("✅ Demo dataset loaded. Explore the features!")
# File uploader
uploaded_file = st.file_uploader("📂 Upload your CSV file", type=["csv"])
# Determine data source
if 'uploaded_file' in st.session_state and not uploaded_file:
# Use demo if no file uploaded
csv_bytes = st.session_state['uploaded_file']
file_name = st.session_state['file_name']
df = pd.read_csv(io.BytesIO(csv_bytes))
st.info(f"Using demo dataset: `{file_name}`")
elif uploaded_file is not None:
df = pd.read_csv(uploaded_file)
st.success("✅ File uploaded successfully.")
else:
df = None
if df is not None:
# Show data preview
with st.expander("🔍 Data Preview (first 10 rows)"):
st.dataframe(df.head(10))
# Basic info
st.subheader("📌 Dataset Information")
col1, col2, col3 = st.columns(3)
col1.metric("Rows", df.shape[0])
col2.metric("Columns", df.shape[1])
col3.metric("Missing Values", df.isnull().sum().sum())
# Identify numeric and categorical columns
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
categorical_cols = df.select_dtypes(include=['object', 'category']).columns.tolist()
if len(numeric_cols) == 0:
st.warning("⚠️ No numeric columns found for statistical analysis.")
else:
st.subheader("📈 Descriptive Statistics")
st.dataframe(df[numeric_cols].describe())
# Histogram
st.subheader("📊 Histogram")
selected_col = st.selectbox("Select a numeric column for histogram:", numeric_cols)
fig_hist = px.histogram(df, x=selected_col, nbins=30, title=f"Histogram of {selected_col}", marginal="box")
st.plotly_chart(fig_hist, use_container_width=True)
# Scatter plot
if len(numeric_cols) >= 2:
st.subheader("📉 Scatter Plot")
col_x = st.selectbox("Select X-axis column:", numeric_cols, key="x")
col_y = st.selectbox("Select Y-axis column:", numeric_cols, key="y")
color_by = st.selectbox("Color by (optional):", [None] + categorical_cols, key="color")
if col_x != col_y:
fig_scatter = px.scatter(df, x=col_x, y=col_y, color=color_by, title=f"{col_x} vs {col_y}")
st.plotly_chart(fig_scatter, use_container_width=True)
else:
st.warning("⚠️ Please select two different columns.")
# Correlation matrix
st.subheader("🔗 Correlation Matrix")
corr = df[numeric_cols].corr()
fig_corr = px.imshow(corr, text_auto=".2f", aspect="auto", title="Correlation Matrix", color_continuous_scale='RdBu_r')
st.plotly_chart(fig_corr, use_container_width=True)
# Missing values per column
st.subheader("🕳️ Missing Values by Column")
nulls = df.isnull().sum()
if nulls.sum() > 0:
fig_nulls = px.bar(nulls, title="Missing Values by Column", labels={'value': 'Count', 'index': 'Column'}, color=nulls)
st.plotly_chart(fig_nulls, use_container_width=True)
else:
st.success("✅ No missing values in the dataset.")
# === STATISTICAL TESTS ===
st.header("🧪 Statistical Tests")
# Independent T-Test (for 2 groups)
if len(numeric_cols) > 0 and len(categorical_cols) > 0:
st.subheader("Independent T-Test (2 groups)")
t_num_col = st.selectbox("Numeric variable:", numeric_cols, key="t_num")
t_cat_col = st.selectbox("Categorical variable (must have exactly 2 groups):",
[col for col in categorical_cols if df[col].nunique() == 2],
key="t_cat")
if t_cat_col:
groups = df[t_cat_col].unique()
if len(groups) == 2:
group1 = df[df[t_cat_col] == groups[0]][t_num_col].dropna()
group2 = df[df[t_cat_col] == groups[1]][t_num_col].dropna()
t_stat, p_val = stats.ttest_ind(group1, group2, equal_var=False)
st.write(f"**T-Test result between `{groups[0]}` and `{groups[1]}` for `{t_num_col}`:**")
st.write(f"- **T-statistic:** {t_stat:.4f}")
st.write(f"- **P-value:** {p_val:.4f}")
if p_val < 0.05:
st.success("🟢 Statistically significant difference (p < 0.05)")
else:
st.error("🔴 No statistically significant difference (p >= 0.05)")
else:
st.warning("Selected categorical variable does not have exactly 2 groups.")
# ANOVA (for 3+ groups)
if len(numeric_cols) > 0 and len(categorical_cols) > 0:
st.subheader("ANOVA (3 or more groups)")
anova_num_col = st.selectbox("Numeric variable:", numeric_cols, key="anova_num")
anova_cat_col = st.selectbox("Categorical variable (3 or more groups):",
[col for col in categorical_cols if df[col].nunique() >= 3],
key="anova_cat")
if anova_cat_col:
groups = [df[df[anova_cat_col] == group][anova_num_col].dropna() for group in df[anova_cat_col].unique()]
if len(groups) >= 3:
f_stat, p_val = stats.f_oneway(*groups)
st.write(f"**ANOVA result for `{anova_num_col}` grouped by `{anova_cat_col}`:**")
st.write(f"- **F-statistic:** {f_stat:.4f}")
st.write(f"- **P-value:** {p_val:.4f}")
if p_val < 0.05:
st.success("🟢 At least one group is significantly different (p < 0.05)")
else:
st.error("🔴 No significant differences between groups (p >= 0.05)")
# Chi-Square Test (between two categorical variables)
if len(categorical_cols) >= 2:
st.subheader("Chi-Square Test (Association between categorical variables)")
chi_col1 = st.selectbox("First categorical variable:", categorical_cols, key="chi1")
chi_col2 = st.selectbox("Second categorical variable:", [col for col in categorical_cols if col != chi_col1], key="chi2")
if chi_col1 and chi_col2:
contingency_table = pd.crosstab(df[chi_col1], df[chi_col2])
chi2, p_val, dof, expected = stats.chi2_contingency(contingency_table)
st.write(f"**Chi-Square result between `{chi_col1}` and `{chi_col2}`:**")
st.write(f"- **Chi² statistic:** {chi2:.4f}")
st.write(f"- **P-value:** {p_val:.4f}")
st.write(f"- **Degrees of freedom:** {dof}")
if p_val < 0.05:
st.success("🟢 Variables are associated (p < 0.05)")
else:
st.error("🔴 No evidence of association between variables (p >= 0.05)")
with st.expander("📋 Contingency Table"):
st.dataframe(contingency_table)
else:
st.info("👆 Upload a CSV file or click 'Load Demo Dataset' to get started.")
# Footer
st.markdown("---")
st.caption(f"© {AUTHOR} | License {LICENSE} | Contact: {EMAIL}")