Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -103,30 +103,71 @@ def read_file_to_df(uploaded) -> pd.DataFrame:
|
|
| 103 |
else:
|
| 104 |
# attempt to read bytes
|
| 105 |
content = uploaded.read()
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
# now path-based read
|
| 111 |
-
if path.lower().endswith(".csv"):
|
| 112 |
-
return pd.read_csv(path)
|
| 113 |
-
elif path.lower().endswith((".xls", ".xlsx")):
|
| 114 |
-
return pd.read_excel(path)
|
| 115 |
-
elif path.lower().endswith(".json"):
|
| 116 |
-
return pd.read_json(path)
|
| 117 |
-
else:
|
| 118 |
-
# try csv then excel then json
|
| 119 |
-
try:
|
| 120 |
-
return pd.read_csv(path)
|
| 121 |
-
except Exception:
|
| 122 |
-
try:
|
| 123 |
-
return pd.read_excel(path)
|
| 124 |
-
except Exception:
|
| 125 |
-
return pd.read_json(path)
|
| 126 |
except Exception as e:
|
| 127 |
-
print("read_file_to_df error:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
raise
|
| 129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
def comprehensive_data_profile(df: pd.DataFrame) -> Dict[str, Any]:
|
| 131 |
"""Enhanced data profiling with comprehensive analysis"""
|
| 132 |
if df is None or df.empty:
|
|
@@ -233,36 +274,38 @@ def detect_outliers_iqr(series):
|
|
| 233 |
return ((series < lower_bound) | (series > upper_bound)).sum()
|
| 234 |
|
| 235 |
def profile_to_enhanced_markdown(profile: Dict[str, Any]) -> str:
|
| 236 |
-
"""Convert comprehensive profile to rich markdown with
|
| 237 |
if not profile:
|
| 238 |
return "No data loaded."
|
| 239 |
|
| 240 |
md = []
|
| 241 |
|
| 242 |
# Header with key metrics
|
| 243 |
-
md.append(
|
| 244 |
md.append("---")
|
| 245 |
|
| 246 |
-
# Dataset Overview
|
| 247 |
-
md.append("##
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
|
|
|
|
|
|
| 252 |
md.append("")
|
| 253 |
|
| 254 |
# Data Quality Scorecard
|
| 255 |
-
md.append("##
|
| 256 |
quality = profile["quality_metrics"]
|
| 257 |
-
md.append(f"
|
| 258 |
-
md.append(f"
|
| 259 |
md.append("")
|
| 260 |
|
| 261 |
# Column Type Summary
|
| 262 |
-
md.append("##
|
| 263 |
-
md.append(f"
|
| 264 |
-
md.append(f"
|
| 265 |
-
md.append(f"
|
| 266 |
md.append("")
|
| 267 |
|
| 268 |
# Detailed Column Analysis
|
|
@@ -761,6 +804,45 @@ css = """
|
|
| 761 |
margin-top: 8px;
|
| 762 |
text-align: center;
|
| 763 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 764 |
"""
|
| 765 |
|
| 766 |
vanta_html = """
|
|
@@ -824,31 +906,36 @@ with gr.Blocks(css=css, title="DataSynth β Analytics Hub") as demo:
|
|
| 824 |
|
| 825 |
# --- Main app (hidden until login) ---
|
| 826 |
with gr.Column(visible=False) as main_area:
|
| 827 |
-
gr.Markdown("#
|
| 828 |
-
with gr.Tabs():
|
| 829 |
-
with gr.TabItem("Data"):
|
| 830 |
-
gr.Markdown("
|
| 831 |
-
upload = gr.File(label="Upload
|
|
|
|
| 832 |
with gr.Row():
|
| 833 |
-
profile_btn = gr.Button("
|
| 834 |
-
view_data_btn = gr.Button("π View Data", variant="secondary")
|
| 835 |
-
download_raw_btn = gr.Button("π₯ Download Raw CSV")
|
| 836 |
with gr.Tabs() as data_tabs:
|
| 837 |
-
with gr.TabItem("Profile Report"):
|
| 838 |
-
profile_md = gr.Markdown(
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
with gr.TabItem("
|
| 844 |
-
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 850 |
|
| 851 |
-
with gr.TabItem("Prepare"):
|
| 852 |
gr.Markdown("Data cleaning and feature engineering options.")
|
| 853 |
with gr.Row():
|
| 854 |
with gr.Column(scale=1):
|
|
@@ -868,12 +955,12 @@ with gr.Blocks(css=css, title="DataSynth β Analytics Hub") as demo:
|
|
| 868 |
with gr.Column(scale=1):
|
| 869 |
prep_output = gr.Markdown("Preparation preview will appear here.")
|
| 870 |
preview_clean = gr.Dataframe(interactive=False)
|
| 871 |
-
with gr.TabItem("Visualize (NL)"):
|
| 872 |
gr.Markdown("Type a natural-language request to create a plot (e.g., 'histogram of age', 'scatter income vs age', 'bar of country').")
|
| 873 |
nl_input = gr.Textbox(label="Describe chart")
|
| 874 |
nl_btn = gr.Button("Create Chart")
|
| 875 |
nl_plot = gr.Plot()
|
| 876 |
-
with gr.TabItem("Model"):
|
| 877 |
gr.Markdown("Choose task, target variable, and model. Scaling and encoding will be applied automatically.")
|
| 878 |
task_select = gr.Radio(choices=["regression","classification"], value="regression", label="Task")
|
| 879 |
target_col = gr.Dropdown(choices=[], label="Target variable")
|
|
@@ -884,7 +971,7 @@ with gr.Blocks(css=css, title="DataSynth β Analytics Hub") as demo:
|
|
| 884 |
model_out_md = gr.Markdown("Model results will show here.")
|
| 885 |
model_feature_imp = gr.Dataframe(interactive=False)
|
| 886 |
model_plots = gr.Plot()
|
| 887 |
-
with gr.TabItem("Report"):
|
| 888 |
gr.Markdown("Generate a short executive report that summarizes profiling, cleaning, and model results.")
|
| 889 |
report_btn = gr.Button("Generate Report")
|
| 890 |
report_download = gr.File(label="Download Report (.md)")
|
|
@@ -907,35 +994,30 @@ with gr.Blocks(css=css, title="DataSynth β Analytics Hub") as demo:
|
|
| 907 |
if uploaded is None:
|
| 908 |
return (gr.update(value="No file uploaded."),
|
| 909 |
pd.DataFrame(), None, None,
|
| 910 |
-
None
|
| 911 |
df = read_file_to_df(uploaded)
|
| 912 |
prof = comprehensive_data_profile(df)
|
| 913 |
md = profile_to_enhanced_markdown(prof)
|
| 914 |
#create visulaizations
|
| 915 |
-
|
| 916 |
-
corr_plot = create_correlation_plot(df, prof)
|
| 917 |
|
| 918 |
# Prepare plot outputs
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
plot3 = plots.get(list(plots.keys())[2]) if len(plots) > 2 else None
|
| 922 |
# prepare choices for date and target selectors
|
| 923 |
#cols = df.columns.tolist()
|
| 924 |
# save df to temp csv for persistence (store path in state)
|
| 925 |
-
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".csv")
|
| 926 |
-
df.to_csv(tmp.name, index=False)
|
| 927 |
|
| 928 |
-
return (md, df.head(100), tmp.name, prof,
|
| 929 |
-
plot1, plot2, plot3, corr_plot)
|
| 930 |
except Exception as e:
|
| 931 |
-
return (gr.update(value=f"Error loading file: {e}"),
|
| 932 |
-
|
| 933 |
-
None, None, None, None)
|
| 934 |
|
| 935 |
profile_btn.click(fn=_load_and_profile,
|
| 936 |
inputs=[upload],
|
| 937 |
-
outputs=[profile_md, sample_table, df_state, profile_state,
|
| 938 |
-
dist_plot1, dist_plot2, dist_plot3, corr_plot])
|
| 939 |
|
| 940 |
def _view_data(uploaded):
|
| 941 |
try:
|
|
|
|
| 103 |
else:
|
| 104 |
# attempt to read bytes
|
| 105 |
content = uploaded.read()
|
| 106 |
+
return _read_bytes_content(content)
|
| 107 |
+
|
| 108 |
+
return _read_file_path(path)
|
| 109 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
except Exception as e:
|
| 111 |
+
print(f"read_file_to_df error: {e}")
|
| 112 |
+
|
| 113 |
+
try:
|
| 114 |
+
if 'path' in locals():
|
| 115 |
+
return pd.read_csv(path, encoding_errors='ignore', engine='python')
|
| 116 |
+
except Exception:
|
| 117 |
+
pass
|
| 118 |
raise
|
| 119 |
|
| 120 |
+
def _read_bytes_content(content) -> pd.DataFrame:
|
| 121 |
+
formats_to_try = [
|
| 122 |
+
('csv', lambda: pd.read_csv(io.BytesIO(content))),
|
| 123 |
+
('csv_utf8', lambda: pd.read_csv(io.BytesIO(content), encoding='utf-8')),
|
| 124 |
+
('csv_latin1', lambda: pd.read_csv(io.BytesIO(content), encoding='latin1')),
|
| 125 |
+
('csv_ignore', lambda: pd.read_csv(io.BytesIO(content), encoding_errors='ignore')),
|
| 126 |
+
('excel', lambda: pd.read_excel(io.BytesIO(content))),
|
| 127 |
+
('json', lambda: pd.read_json(io.BytesIO(content))),
|
| 128 |
+
]
|
| 129 |
+
for format_name, reader_func in formats_to_try:
|
| 130 |
+
try:
|
| 131 |
+
return reader_func()
|
| 132 |
+
except Exception as e:
|
| 133 |
+
print(f"Failed to read as {format_name}: {e}")
|
| 134 |
+
continue
|
| 135 |
+
|
| 136 |
+
raise ValueError("Could not read file with any supported format")
|
| 137 |
+
|
| 138 |
+
def _read_file_path(path) -> pd.DataFrame:
|
| 139 |
+
"""Handle file path reading with multiple format attempts"""
|
| 140 |
+
file_ext = os.path.splitext(path)[1].lower()
|
| 141 |
+
|
| 142 |
+
if file_ext in ['.csv', '.txt', '.tsv']:
|
| 143 |
+
encodings = [None, 'utf-8', 'latin1', 'cp1252', 'iso-8859-1']
|
| 144 |
+
for encoding in encodings:
|
| 145 |
+
try:
|
| 146 |
+
if file_ext == '.tsv':
|
| 147 |
+
return pd.read_csv(path, sep='\t', encoding=encoding, encoding_errors='ignore')
|
| 148 |
+
else:
|
| 149 |
+
return pd.read_csv(path, encoding=encoding, encoding_errors='ignore')
|
| 150 |
+
except Exception:
|
| 151 |
+
continue
|
| 152 |
+
|
| 153 |
+
elif file_ext in ['.xlsx', '.xls', '.xlsm', '.xlsb']:
|
| 154 |
+
return pd.read_excel(path)
|
| 155 |
+
|
| 156 |
+
elif file_ext == '.json':
|
| 157 |
+
return pd.read_json(path)
|
| 158 |
+
|
| 159 |
+
elif file_ext in ['.parquet']:
|
| 160 |
+
return pd.read_parquet(path)
|
| 161 |
+
|
| 162 |
+
elif file_ext in ['.feather']:
|
| 163 |
+
return pd.read_feather(path)
|
| 164 |
+
|
| 165 |
+
# Final fallback
|
| 166 |
+
try:
|
| 167 |
+
return pd.read_csv(path, encoding_errors='ignore', engine='python')
|
| 168 |
+
except Exception:
|
| 169 |
+
return pd.read_excel(path)
|
| 170 |
+
|
| 171 |
def comprehensive_data_profile(df: pd.DataFrame) -> Dict[str, Any]:
|
| 172 |
"""Enhanced data profiling with comprehensive analysis"""
|
| 173 |
if df is None or df.empty:
|
|
|
|
| 274 |
return ((series < lower_bound) | (series > upper_bound)).sum()
|
| 275 |
|
| 276 |
def profile_to_enhanced_markdown(profile: Dict[str, Any]) -> str:
|
| 277 |
+
"""Convert comprehensive profile to rich markdown with better formatting"""
|
| 278 |
if not profile:
|
| 279 |
return "No data loaded."
|
| 280 |
|
| 281 |
md = []
|
| 282 |
|
| 283 |
# Header with key metrics
|
| 284 |
+
md.append("# π Comprehensive Data Profile Report")
|
| 285 |
md.append("---")
|
| 286 |
|
| 287 |
+
# Dataset Overview in a compact grid
|
| 288 |
+
md.append("## π― Dataset Overview")
|
| 289 |
+
with io.StringIO() as overview:
|
| 290 |
+
overview.write(f"**Rows:** {profile['rows']:,} | ")
|
| 291 |
+
overview.write(f"**Columns:** {profile['columns']} | ")
|
| 292 |
+
overview.write(f"**Memory:** {profile['memory_usage']:.2f} MB | ")
|
| 293 |
+
overview.write(f"**Duplicates:** {profile['duplicate_rows']} ({profile['duplicate_pct']:.2%})")
|
| 294 |
+
md.append(overview.getvalue())
|
| 295 |
md.append("")
|
| 296 |
|
| 297 |
# Data Quality Scorecard
|
| 298 |
+
md.append("## π Data Quality Scorecard")
|
| 299 |
quality = profile["quality_metrics"]
|
| 300 |
+
md.append(f"**Completeness:** {quality['completeness']:.2%} | ")
|
| 301 |
+
md.append(f"**Uniqueness:** {quality['uniqueness']:.2%}")
|
| 302 |
md.append("")
|
| 303 |
|
| 304 |
# Column Type Summary
|
| 305 |
+
md.append("## ποΈ Column Type Summary")
|
| 306 |
+
md.append(f"**Numeric:** {len(profile['numeric_cols'])} | ")
|
| 307 |
+
md.append(f"**Categorical:** {len(profile['categorical_cols'])} | ")
|
| 308 |
+
md.append(f"**DateTime:** {len(profile['datetime_cols'])}")
|
| 309 |
md.append("")
|
| 310 |
|
| 311 |
# Detailed Column Analysis
|
|
|
|
| 804 |
margin-top: 8px;
|
| 805 |
text-align: center;
|
| 806 |
}
|
| 807 |
+
|
| 808 |
+
/* Improved table styling for profile report */
|
| 809 |
+
#profile-report table {
|
| 810 |
+
width: 100%;
|
| 811 |
+
border-collapse: collapse;
|
| 812 |
+
font-size: 14px;
|
| 813 |
+
table-layout: fixed;
|
| 814 |
+
}
|
| 815 |
+
|
| 816 |
+
#profile-report th, #profile-report td {
|
| 817 |
+
padding: 8px 12px;
|
| 818 |
+
border: 1px solid #ddd;
|
| 819 |
+
text-align: left;
|
| 820 |
+
word-wrap: break-word;
|
| 821 |
+
white-space: nowrap;
|
| 822 |
+
overflow: hidden;
|
| 823 |
+
text-overflow: ellipsis;
|
| 824 |
+
}
|
| 825 |
+
|
| 826 |
+
#profile-report th {
|
| 827 |
+
background-color: #f5f5f5;
|
| 828 |
+
font-weight: 600;
|
| 829 |
+
}
|
| 830 |
+
|
| 831 |
+
#profile-report tr:nth-child(even) {
|
| 832 |
+
background-color: #f9f9f9;
|
| 833 |
+
}
|
| 834 |
+
|
| 835 |
+
/* Ensure markdown content uses full width */
|
| 836 |
+
.markdown-body {
|
| 837 |
+
max-width: 100% !important;
|
| 838 |
+
}
|
| 839 |
+
|
| 840 |
+
/* Better scrolling for large content */
|
| 841 |
+
.gr-markdown {
|
| 842 |
+
max-height: 70vh;
|
| 843 |
+
overflow-y: auto;
|
| 844 |
+
}
|
| 845 |
+
|
| 846 |
"""
|
| 847 |
|
| 848 |
vanta_html = """
|
|
|
|
| 906 |
|
| 907 |
# --- Main app (hidden until login) ---
|
| 908 |
with gr.Column(visible=False) as main_area:
|
| 909 |
+
gr.Markdown("# π DataSynth Analytics Workspace")
|
| 910 |
+
with gr.Tabs() as main_tabs:
|
| 911 |
+
with gr.TabItem("π Data Profiling"):
|
| 912 |
+
gr.Markdown("### π Upload & Analyze Your Dataset")
|
| 913 |
+
upload = gr.File(label="Upload Dataset", file_types=[".csv", ".xlsx", ".xls", ".xlsm", ".xlsb", ".json", ".parquet", ".feather", ".txt", ".tsv"],
|
| 914 |
+
type="filepath")
|
| 915 |
with gr.Row():
|
| 916 |
+
profile_btn = gr.Button("π Run Comprehensive Data Profiling", variant="primary", size="lg")
|
| 917 |
+
#view_data_btn = gr.Button("π View Data", variant="secondary")
|
| 918 |
+
#download_raw_btn = gr.Button("π₯ Download Raw CSV")
|
| 919 |
with gr.Tabs() as data_tabs:
|
| 920 |
+
with gr.TabItem("π Profile Report"):
|
| 921 |
+
profile_md = gr.Markdown(
|
| 922 |
+
"No dataset loaded. Upload your data and click 'Run Comprehensive Data Profiling'.",
|
| 923 |
+
elem_id="profile-report"
|
| 924 |
+
)
|
| 925 |
+
|
| 926 |
+
with gr.TabItem("ποΈ Data Preview"):
|
| 927 |
+
sample_table = gr.Dataframe(
|
| 928 |
+
interactive=False,
|
| 929 |
+
label="Sample Data (First 100 rows)",
|
| 930 |
+
height = 400
|
| 931 |
+
)
|
| 932 |
+
|
| 933 |
+
with gr.TabItem("π Visualizations"):
|
| 934 |
+
gr.Markdown("### Correlation Analysis")
|
| 935 |
+
corr_plot = gr.Plot(label="Correlation Heatmap")
|
| 936 |
+
gr.Markdown("*Note: Only numeric columns are included in correlation analysis*")
|
| 937 |
|
| 938 |
+
with gr.TabItem("π§ Prepare"):
|
| 939 |
gr.Markdown("Data cleaning and feature engineering options.")
|
| 940 |
with gr.Row():
|
| 941 |
with gr.Column(scale=1):
|
|
|
|
| 955 |
with gr.Column(scale=1):
|
| 956 |
prep_output = gr.Markdown("Preparation preview will appear here.")
|
| 957 |
preview_clean = gr.Dataframe(interactive=False)
|
| 958 |
+
with gr.TabItem("π Visualize (NL)"):
|
| 959 |
gr.Markdown("Type a natural-language request to create a plot (e.g., 'histogram of age', 'scatter income vs age', 'bar of country').")
|
| 960 |
nl_input = gr.Textbox(label="Describe chart")
|
| 961 |
nl_btn = gr.Button("Create Chart")
|
| 962 |
nl_plot = gr.Plot()
|
| 963 |
+
with gr.TabItem("π€ Model"):
|
| 964 |
gr.Markdown("Choose task, target variable, and model. Scaling and encoding will be applied automatically.")
|
| 965 |
task_select = gr.Radio(choices=["regression","classification"], value="regression", label="Task")
|
| 966 |
target_col = gr.Dropdown(choices=[], label="Target variable")
|
|
|
|
| 971 |
model_out_md = gr.Markdown("Model results will show here.")
|
| 972 |
model_feature_imp = gr.Dataframe(interactive=False)
|
| 973 |
model_plots = gr.Plot()
|
| 974 |
+
with gr.TabItem("π Report"):
|
| 975 |
gr.Markdown("Generate a short executive report that summarizes profiling, cleaning, and model results.")
|
| 976 |
report_btn = gr.Button("Generate Report")
|
| 977 |
report_download = gr.File(label="Download Report (.md)")
|
|
|
|
| 994 |
if uploaded is None:
|
| 995 |
return (gr.update(value="No file uploaded."),
|
| 996 |
pd.DataFrame(), None, None,
|
| 997 |
+
None)
|
| 998 |
df = read_file_to_df(uploaded)
|
| 999 |
prof = comprehensive_data_profile(df)
|
| 1000 |
md = profile_to_enhanced_markdown(prof)
|
| 1001 |
#create visulaizations
|
| 1002 |
+
corr_plot_fig = create_correlation_plot(df, prof)
|
|
|
|
| 1003 |
|
| 1004 |
# Prepare plot outputs
|
| 1005 |
+
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".csv")
|
| 1006 |
+
df.to_csv(tmp.name, index=False)
|
|
|
|
| 1007 |
# prepare choices for date and target selectors
|
| 1008 |
#cols = df.columns.tolist()
|
| 1009 |
# save df to temp csv for persistence (store path in state)
|
| 1010 |
+
#tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".csv")
|
| 1011 |
+
#df.to_csv(tmp.name, index=False)
|
| 1012 |
|
| 1013 |
+
return (md, df.head(100), tmp.name, prof, corr_plot_fig)
|
|
|
|
| 1014 |
except Exception as e:
|
| 1015 |
+
return (gr.update(value=f"Error loading file: {e}"),
|
| 1016 |
+
pd.DataFrame(), None, None, None)
|
|
|
|
| 1017 |
|
| 1018 |
profile_btn.click(fn=_load_and_profile,
|
| 1019 |
inputs=[upload],
|
| 1020 |
+
outputs=[profile_md, sample_table, df_state, profile_state, corr_plot])
|
|
|
|
| 1021 |
|
| 1022 |
def _view_data(uploaded):
|
| 1023 |
try:
|