/
dayekb
/
vibecoding_visualize_csv
Обзор
Документация
Войти
/
dayekb
/
vibecoding_visualize_csv
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
interactive_csv_visualizer.py
798 строк
28 KB
dayekb
upload files
20 авг 2025, 18:31
20 авг 2025, 18:31
81ccc4f
Код
Авторство
О чём код?
""" Interactive CSV Visualization Application ====================================== A modern, interactive CSV visualization app using Plotly and Dash. Features include: - Interactive plots with zoom, pan, and hover - Real-time data filtering and exploration - Beautiful, responsive design - Multiple plot types with advanced customization - Data export capabilities This version is much more interactive and modern than the matplotlib version! """ import dash from dash import dcc, html, Input, Output, State, callback_context import plotly.express as px import plotly.graph_objects as go import plotly.subplots as sp import pandas as pd import numpy as np import base64 import io from datetime import datetime import os # Initialize the Dash app app = dash.Dash(__name__, title="Interactive CSV Visualizer") app.config.suppress_callback_exceptions = True # Global variable to store the loaded data global_data = None global_filename = "No file loaded" def create_sample_data(): """ Create a larger, more realistic Titanic dataset for demonstration. This shows the power of interactive visualizations with more data. """ np.random.seed(42) n_passengers = 1000 # Generate realistic Titanic-like data data = pd.DataFrame({ 'PassengerId': range(1, n_passengers + 1), 'Survived': np.random.choice([0, 1], n_passengers, p=[0.62, 0.38]), 'Pclass': np.random.choice([1, 2, 3], n_passengers, p=[0.24, 0.21, 0.55]), 'Sex': np.random.choice(['male', 'female'], n_passengers, p=[0.65, 0.35]), 'Age': np.random.normal(29.7, 14.5, n_passengers).clip(0, 80), 'SibSp': np.random.poisson(0.5, n_passengers), 'Parch': np.random.poisson(0.4, n_passengers), 'Fare': np.random.exponential(32.2, n_passengers), 'Embarked': np.random.choice(['S', 'C', 'Q'], n_passengers, p=[0.72, 0.19, 0.09]), 'Title': np.random.choice(['Mr.', 'Mrs.', 'Miss.', 'Master.', 'Dr.'], n_passengers, p=[0.4, 0.2, 0.15, 0.1, 0.05]) }) # Add some missing values to simulate real data data.loc[np.random.choice(data.index, size=100), 'Age'] = np.nan data.loc[np.random.choice(data.index, size=50), 'Cabin'] = np.nan return data def create_app_layout(): """ Create the main application layout with modern, responsive design. """ return html.Div([ # Header with title and styling html.Div([ html.H1("🚢 Interactive CSV Visualizer", className="app-header"), html.P("Explore your data with beautiful, interactive visualizations", className="app-subtitle") ], className="header-section"), # Main content area html.Div([ # Left sidebar for controls html.Div([ # File upload section html.Div([ html.H3("📁 Data Loading", className="section-title"), dcc.Upload( id='upload-data', children=html.Div([ 'Drag and Drop or ', html.A('Select a CSV File') ]), style={ 'width': '100%', 'height': '60px', 'lineHeight': '60px', 'borderWidth': '1px', 'borderStyle': 'dashed', 'borderRadius': '5px', 'textAlign': 'center', 'margin': '10px' }, multiple=False ), html.Button("Load Sample Titanic Data", id="load-sample-btn", className="btn btn-primary"), html.Div(id="file-info", className="file-info") ], className="control-section"), # Data filtering section html.Div([ html.H3("🔍 Data Filters", className="section-title"), html.Label("Filter by Survival:"), dcc.Dropdown( id='survival-filter', options=[ {'label': 'All Passengers', 'value': 'all'}, {'label': 'Survived Only', 'value': 1}, {'label': 'Died Only', 'value': 0} ], value='all', className="dropdown" ), html.Label("Filter by Passenger Class:"), dcc.Dropdown( id='class-filter', options=[ {'label': 'All Classes', 'value': 'all'}, {'label': 'First Class', 'value': 1}, {'label': 'Second Class', 'value': 2}, {'label': 'Third Class', 'value': 3} ], value='all', className="dropdown" ), html.Label("Filter by Gender:"), dcc.Dropdown( id='gender-filter', options=[ {'label': 'All Genders', 'value': 'all'}, {'label': 'Male', 'value': 'male'}, {'label': 'Female', 'value': 'female'} ], value='all', className="dropdown" ), html.Label("Age Range:"), dcc.RangeSlider( id='age-range', min=0, max=80, step=1, value=[0, 80], marks={i: str(i) for i in range(0, 81, 10)}, className="range-slider" ) ], className="control-section"), # Visualization controls html.Div([ html.H3("📊 Plot Controls", className="section-title"), html.Label("Plot Type:"), dcc.Dropdown( id='plot-type', options=[ {'label': 'Scatter Plot', 'value': 'scatter'}, {'label': 'Histogram', 'value': 'histogram'}, {'label': 'Box Plot', 'value': 'boxplot'}, {'label': 'Violin Plot', 'value': 'violin'}, {'label': '3D Scatter', 'value': '3d-scatter'}, {'label': 'Correlation Heatmap', 'value': 'heatmap'}, {'label': 'Faceted Plots', 'value': 'faceted'} ], value='scatter', className="dropdown" ), html.Label("X-Axis Feature:"), dcc.Dropdown(id='x-feature', className="dropdown"), html.Label("Y-Axis Feature:"), dcc.Dropdown(id='y-feature', className="dropdown"), html.Label("Color By:"), dcc.Dropdown(id='color-feature', className="dropdown"), html.Label("Size By (for scatter plots):"), dcc.Dropdown(id='size-feature', className="dropdown"), html.Label("Number of Bins (for histograms):"), dcc.Slider( id='bins-slider', min=5, max=50, step=5, value=20, marks={i: str(i) for i in range(5, 51, 10)}, className="slider" ), html.Button("Create Plot", id="create-plot-btn", className="btn btn-success") ], className="control-section"), # Export section html.Div([ html.H3("💾 Export Options", className="section-title"), html.Button("Export Data as CSV", id="export-csv-btn", className="btn btn-info"), html.Button("Export Plot as HTML", id="export-plot-btn", className="btn btn-warning"), dcc.Download(id="download-csv"), dcc.Download(id="download-plot") ], className="control-section") ], className="sidebar"), # Right main content area html.Div([ # Data overview section html.Div([ html.H3("📋 Data Overview", className="section-title"), html.Div(id="data-overview", className="data-overview") ], className="main-section"), # Plot display area html.Div([ html.H3("📈 Interactive Plot", className="section-title"), html.Div(id="plot-container", className="plot-container") ], className="main-section"), # Statistics section html.Div([ html.H3("📊 Statistical Summary", className="section-title"), html.Div(id="stats-container", className="stats-container") ], className="main-section") ], className="main-content") ], className="content-area") ], className="app-container") def parse_contents(contents, filename): """ Parse uploaded CSV file contents. """ global global_data, global_filename content_type, content_string = contents.split(',') decoded = base64.b64decode(content_string) try: if 'csv' in filename: df = pd.read_csv(io.StringIO(decoded.decode('utf-8'))) elif 'xls' in filename: df = pd.read_excel(io.BytesIO(decoded)) else: return html.Div(['Please upload a CSV or Excel file.']) global_data = df global_filename = filename return html.Div([ html.H5(f'Successfully loaded: {filename}'), html.H6(f'Shape: {df.shape[0]} rows × {df.shape[1]} columns'), html.P(f'Memory usage: {df.memory_usage(deep=True).sum() / 1024:.2f} KB') ]) except Exception as e: return html.Div([ html.H5('Error processing this file.'), html.P(str(e)) ]) def create_interactive_plot(plot_type, x_feature, y_feature, color_feature, size_feature, bins, filtered_data): """ Create interactive plots using Plotly. """ if filtered_data is None or filtered_data.empty: return go.Figure().add_annotation( text="No data available for plotting", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False ) if plot_type == 'scatter': fig = px.scatter( filtered_data, x=x_feature, y=y_feature, color=color_feature if color_feature != 'None' else None, size=size_feature if size_feature != 'None' else None, hover_data=['PassengerId', 'Name'] if 'Name' in filtered_data.columns else None, title=f"{x_feature} vs {y_feature}", template="plotly_white" ) elif plot_type == 'histogram': fig = px.histogram( filtered_data, x=x_feature, color=color_feature if color_feature != 'None' else None, nbins=bins, title=f"Distribution of {x_feature}", template="plotly_white" ) elif plot_type == 'boxplot': fig = px.box( filtered_data, x=x_feature, y=y_feature, color=color_feature if color_feature != 'None' else None, title=f"{y_feature} by {x_feature}", template="plotly_white" ) elif plot_type == 'violin': fig = px.violin( filtered_data, x=x_feature, y=y_feature, color=color_feature if color_feature != 'None' else None, title=f"{y_feature} Distribution by {x_feature}", template="plotly_white" ) elif plot_type == '3d-scatter': fig = px.scatter_3d( filtered_data, x=x_feature, y=y_feature, z=color_feature if color_feature != 'None' else 'Age', color=color_feature if color_feature != 'None' else 'Sex', title=f"3D Scatter: {x_feature} vs {y_feature} vs {color_feature}", template="plotly_white" ) elif plot_type == 'heatmap': # Create correlation matrix for numerical columns numeric_data = filtered_data.select_dtypes(include=[np.number]) if len(numeric_data.columns) > 1: corr_matrix = numeric_data.corr() fig = px.imshow( corr_matrix, title="Correlation Matrix", template="plotly_white", color_continuous_scale="RdBu" ) else: fig = go.Figure().add_annotation( text="Need at least 2 numerical columns for correlation", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False ) elif plot_type == 'faceted': if color_feature != 'None': fig = px.scatter( filtered_data, x=x_feature, y=y_feature, color=color_feature, facet_col=color_feature, title=f"{x_feature} vs {y_feature} by {color_feature}", template="plotly_white" ) else: fig = px.scatter( filtered_data, x=x_feature, y=y_feature, title=f"{x_feature} vs {y_feature}", template="plotly_white" ) # Enhance the plot with better styling fig.update_layout( title_x=0.5, title_font_size=20, showlegend=True, height=600, margin=dict(l=50, r=50, t=80, b=50) ) return fig def get_data_overview(data): """ Create a comprehensive data overview display. """ if data is None: return html.Div("No data loaded") overview_html = [] # Basic info overview_html.append(html.H4("📊 Dataset Information")) overview_html.append(html.P(f"Shape: {data.shape[0]} rows × {data.shape[1]} columns")) overview_html.append(html.P(f"Memory usage: {data.memory_usage(deep=True).sum() / 1024:.2f} KB")) # Column information overview_html.append(html.H4("📋 Column Details")) for col in data.columns: col_info = f"{col}: {data[col].dtype}" if data[col].dtype == 'object' or data[col].nunique() < 10: unique_vals = data[col].value_counts().head(5) col_info += f" | Sample: {dict(unique_vals)}" elif pd.api.types.is_numeric_dtype(data[col]): col_info += f" | Range: {data[col].min():.2f} to {data[col].max():.2f}" col_info += f" | Mean: {data[col].mean():.2f}" overview_html.append(html.P(col_info, className="col-info")) # Missing values missing_counts = data.isnull().sum() if missing_counts.sum() > 0: overview_html.append(html.H4("⚠️ Missing Values")) for col in data.columns: if missing_counts[col] > 0: overview_html.append(html.P(f"{col}: {missing_counts[col]} missing")) return html.Div(overview_html) def get_statistical_summary(data): """ Create a statistical summary of the data. """ if data is None: return html.Div("No data loaded") stats_html = [] # Numerical summary numeric_cols = data.select_dtypes(include=[np.number]).columns if len(numeric_cols) > 0: stats_html.append(html.H4("📈 Numerical Statistics")) for col in numeric_cols: col_stats = data[col].describe() stats_html.append(html.H5(col)) stats_html.append(html.P(f"Count: {col_stats['count']:.0f}")) stats_html.append(html.P(f"Mean: {col_stats['mean']:.2f}")) stats_html.append(html.P(f"Std: {col_stats['std']:.2f}")) stats_html.append(html.P(f"Min: {col_stats['min']:.2f}")) stats_html.append(html.P(f"25%: {col_stats['25%']:.2f}")) stats_html.append(html.P(f"50%: {col_stats['50%']:.2f}")) stats_html.append(html.P(f"75%: {col_stats['75%']:.2f}")) stats_html.append(html.P(f"Max: {col_stats['max']:.2f}")) stats_html.append(html.Hr()) # Categorical summary cat_cols = data.select_dtypes(include=['object']).columns if len(cat_cols) > 0: stats_html.append(html.H4("📝 Categorical Summary")) for col in cat_cols: value_counts = data[col].value_counts() stats_html.append(html.H5(col)) for val, count in value_counts.head(10).items(): stats_html.append(html.P(f"{val}: {count} ({count/len(data)*100:.1f}%)")) stats_html.append(html.Hr()) return html.Div(stats_html) # Callback functions @app.callback( Output('file-info', 'children'), Input('upload-data', 'contents'), State('upload-data', 'filename') ) def update_file_info(contents, filename): if contents is not None: return parse_contents(contents, filename) return "No file uploaded" @app.callback( Output('data-overview', 'children'), Input('load-sample-btn', 'n_clicks'), Input('upload-data', 'contents'), prevent_initial_call=True ) def update_data_overview(n_clicks, contents): global global_data if n_clicks: global_data = create_sample_data() return get_data_overview(global_data) elif contents: return get_data_overview(global_data) return "No data loaded" @app.callback( [Output('x-feature', 'options'), Output('y-feature', 'options'), Output('color-feature', 'options'), Output('size-feature', 'options')], [Input('load-sample-btn', 'n_clicks'), Input('upload-data', 'contents')], prevent_initial_call=True ) def update_feature_dropdowns(n_clicks, contents): global global_data if global_data is not None: columns = [{'label': col, 'value': col} for col in global_data.columns] none_option = [{'label': 'None', 'value': 'None'}] return columns, columns, none_option + columns, none_option + columns return [], [], [], [] @app.callback( Output('plot-container', 'children'), [Input('create-plot-btn', 'n_clicks'), Input('survival-filter', 'value'), Input('class-filter', 'value'), Input('gender-filter', 'value'), Input('age-range', 'value')], [State('plot-type', 'value'), State('x-feature', 'value'), State('y-feature', 'value'), State('color-feature', 'value'), State('size-feature', 'value'), State('bins-slider', 'value')], prevent_initial_call=True ) def update_plot(n_clicks, survival_filter, class_filter, gender_filter, age_range, plot_type, x_feature, y_feature, color_feature, size_feature, bins): global global_data if global_data is None or not all([plot_type, x_feature]): return html.Div("Please load data and select plot parameters") # Apply filters filtered_data = global_data.copy() if survival_filter != 'all': filtered_data = filtered_data[filtered_data['Survived'] == survival_filter] if class_filter != 'all': filtered_data = filtered_data[filtered_data['Pclass'] == class_filter] if gender_filter != 'all': filtered_data = filtered_data[filtered_data['Sex'] == gender_filter] if age_range: filtered_data = filtered_data[ (filtered_data['Age'] >= age_range[0]) & (filtered_data['Age'] <= age_range[1]) ] # Create the plot fig = create_interactive_plot( plot_type, x_feature, y_feature, color_feature, size_feature, bins, filtered_data ) return dcc.Graph( id='main-plot', figure=fig, config={'displayModeBar': True, 'displaylogo': False} ) @app.callback( Output('stats-container', 'children'), [Input('survival-filter', 'value'), Input('class-filter', 'value'), Input('gender-filter', 'value'), Input('age-range', 'value')], prevent_initial_call=True ) def update_stats(survival_filter, class_filter, gender_filter, age_range): global global_data if global_data is None: return "No data loaded" # Apply the same filters filtered_data = global_data.copy() if survival_filter != 'all': filtered_data = filtered_data[filtered_data['Survived'] == survival_filter] if class_filter != 'all': filtered_data = filtered_data[filtered_data['Pclass'] == class_filter] if gender_filter != 'all': filtered_data = filtered_data[filtered_data['Sex'] == gender_filter] if age_range: filtered_data = filtered_data[ (filtered_data['Age'] >= age_range[0]) & (filtered_data['Age'] <= age_range[1]) ] return get_statistical_summary(filtered_data) @app.callback( Output('download-csv', 'data'), Input('export-csv-btn', 'n_clicks'), prevent_initial_call=True ) def export_csv(n_clicks): global global_data if global_data is None: return None return dcc.send_data_frame(global_data.to_csv, "titanic_data.csv") @app.callback( Output('download-plot', 'data'), Input('export-plot-btn', 'n_clicks'), prevent_initial_call=True ) def export_plot(n_clicks): # This would export the current plot as HTML # For now, return a simple message return None # Set up the app layout app.layout = create_app_layout() # Add custom CSS for better styling app.index_string = ''' <!DOCTYPE html> <html> <head> <title>Interactive CSV Visualizer</title> <style> .app-container { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; } .header-section { text-align: center; color: white; margin-bottom: 30px; } .app-header { font-size: 3rem; margin-bottom: 10px; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); } .app-subtitle { font-size: 1.2rem; opacity: 0.9; } .content-area { display: flex; gap: 20px; max-width: 1400px; margin: 0 auto; } .sidebar { width: 350px; background: white; border-radius: 15px; padding: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); height: fit-content; } .main-content { flex: 1; display: flex; flex-direction: column; gap: 20px; } .main-section { background: white; border-radius: 15px; padding: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); } .section-title { color: #333; margin-bottom: 15px; border-bottom: 2px solid #667eea; padding-bottom: 10px; } .control-section { margin-bottom: 25px; padding: 15px; background: #f8f9fa; border-radius: 10px; } .dropdown, .slider, .range-slider { margin-bottom: 15px; } .btn { width: 100%; padding: 12px; margin: 5px 0; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; } .btn-primary { background: #667eea; color: white; } .btn-primary:hover { background: #5a6fd8; transform: translateY(-2px); } .btn-success { background: #28a745; color: white; } .btn-success:hover { background: #218838; transform: translateY(-2px); } .btn-info { background: #17a2b8; color: white; } .btn-warning { background: #ffc107; color: #212529; } .file-info { margin-top: 10px; padding: 10px; background: #e9ecef; border-radius: 5px; font-size: 14px; } .data-overview, .stats-container { max-height: 400px; overflow-y: auto; } .col-info { margin: 5px 0; padding: 5px; background: #f8f9fa; border-radius: 3px; font-family: 'Courier New', monospace; font-size: 12px; } .plot-container { min-height: 600px; } label { font-weight: 600; color: #555; margin-bottom: 5px; display: block; } </style> </head> <body> <div id="react-entry-point"></div> </body> </html> ''' if __name__ == '__main__': print("🚀 Starting Interactive CSV Visualizer...") print("📱 Open your web browser and go to: http://127.0.0.1:8050") print("💡 This version features interactive plots with zoom, pan, and hover!") app.run_server(debug=True, host='127.0.0.1', port=8050)