/
dayekb
/
vibecoding_visualize_csv
Обзор
Документация
Войти
/
dayekb
/
vibecoding_visualize_csv
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
csv_visualizer.py
667 строк
27 KB
dayekb
upload files
20 авг 2025, 18:31
20 авг 2025, 18:31
81ccc4f
Код
Авторство
О чём код?
""" CSV Visualization Application ============================ This application allows users to: 1. Load CSV files and explore their structure 2. Create visualizations for numerical features (scatter plots, histograms) 3. Create visualizations for categorical features with color coding 4. Save generated plots as images The app uses: - pandas: for data manipulation and analysis - matplotlib: for creating plots and charts - seaborn: for enhanced statistical visualizations - tkinter: for the graphical user interface Author: Learning Project """ import tkinter as tk from tkinter import ttk, filedialog, messagebox import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import numpy as np import os class CSVVisualizer: """ Main class for the CSV visualization application. This class handles the GUI creation, data loading, and plotting functionality. """ def __init__(self, root): """ Initialize the application with the main window. Args: root: The main tkinter window """ self.root = root self.root.title("CSV Data Visualizer - Titanic Example") self.root.geometry("1200x800") # Initialize data storage self.data = None # Will store the loaded CSV data self.current_figure = None # Will store the current matplotlib figure # Set up the user interface self.setup_ui() # Load the sample Titanic dataset by default self.load_sample_data() def setup_ui(self): """ Create and organize the user interface elements. This method sets up all the buttons, dropdowns, and display areas. """ # Create main frame with padding main_frame = ttk.Frame(self.root, padding="10") main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # Configure grid weights for responsive layout self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) main_frame.columnconfigure(1, weight=1) main_frame.rowconfigure(3, weight=1) # File loading section self.create_file_section(main_frame) # Data information section self.create_data_info_section(main_frame) # Visualization controls section self.create_viz_controls_section(main_frame) # Plot display section self.create_plot_section(main_frame) # Save controls section self.create_save_section(main_frame) def create_file_section(self, parent): """ Create the file loading section with buttons and file path display. Args: parent: The parent widget to attach this section to """ # File section label ttk.Label(parent, text="File Operations", font=("Arial", 12, "bold")).grid( row=0, column=0, columnspan=2, sticky=tk.W, pady=(0, 10) ) # Load file button ttk.Button(parent, text="Load CSV File", command=self.load_csv_file).grid( row=1, column=0, sticky=tk.W, padx=(0, 10) ) # Load sample data button ttk.Button(parent, text="Load Sample Titanic Data", command=self.load_sample_data).grid( row=1, column=1, sticky=tk.W ) # File path display self.file_path_var = tk.StringVar(value="No file loaded") ttk.Label(parent, textvariable=self.file_path_var, font=("Arial", 9)).grid( row=2, column=0, columnspan=2, sticky=tk.W, pady=(5, 0) ) def create_data_info_section(self, parent): """ Create the data information display section showing dataset details. Args: parent: The parent widget to attach this section to """ # Data info section label ttk.Label(parent, text="Dataset Information", font=("Arial", 12, "bold")).grid( row=3, column=0, columnspan=2, sticky=tk.W, pady=(20, 10) ) # Create a frame for data info with scrollbar info_frame = ttk.Frame(parent) info_frame.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 20)) # Text widget for displaying data info self.info_text = tk.Text(info_frame, height=8, width=80, font=("Consolas", 9)) info_scrollbar = ttk.Scrollbar(info_frame, orient=tk.VERTICAL, command=self.info_text.yview) self.info_text.configure(yscrollcommand=info_scrollbar.set) self.info_text.grid(row=0, column=0, sticky=(tk.W, tk.E)) info_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S)) # Configure grid weights info_frame.columnconfigure(0, weight=1) def create_viz_controls_section(self, parent): """ Create the visualization controls section with dropdowns and plot type selection. Args: parent: The parent widget to attach this section to """ # Visualization controls section label ttk.Label(parent, text="Visualization Controls", font=("Arial", 12, "bold")).grid( row=5, column=0, columnspan=2, sticky=tk.W, pady=(20, 10) ) # Create a frame for controls controls_frame = ttk.Frame(parent) controls_frame.grid(row=6, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 20)) # X-axis feature selection ttk.Label(controls_frame, text="X-axis Feature:").grid(row=0, column=0, sticky=tk.W, padx=(0, 10)) self.x_feature_var = tk.StringVar() self.x_feature_combo = ttk.Combobox(controls_frame, textvariable=self.x_feature_var, state="readonly") self.x_feature_combo.grid(row=0, column=1, sticky=tk.W, padx=(0, 20)) # Y-axis feature selection (for scatter plots) ttk.Label(controls_frame, text="Y-axis Feature:").grid(row=0, column=2, sticky=tk.W, padx=(0, 10)) self.y_feature_var = tk.StringVar() self.y_feature_combo = ttk.Combobox(controls_frame, textvariable=self.y_feature_var, state="readonly") self.y_feature_combo.grid(row=0, column=3, sticky=tk.W, padx=(0, 20)) # Color by feature selection (for categorical coloring) ttk.Label(controls_frame, text="Color by:").grid(row=0, column=4, sticky=tk.W, padx=(0, 10)) self.color_feature_var = tk.StringVar() self.color_feature_combo = ttk.Combobox(controls_frame, textvariable=self.color_feature_var, state="readonly") self.color_feature_combo.grid(row=0, column=5, sticky=tk.W) # Plot type selection ttk.Label(controls_frame, text="Plot Type:").grid(row=1, column=0, sticky=tk.W, padx=(0, 10), pady=(10, 0)) self.plot_type_var = tk.StringVar(value="scatter") plot_type_frame = ttk.Frame(controls_frame) plot_type_frame.grid(row=1, column=1, columnspan=2, sticky=tk.W, pady=(10, 0)) ttk.Radiobutton(plot_type_frame, text="Scatter Plot", variable=self.plot_type_var, value="scatter", command=self.on_plot_type_change).pack(side=tk.LEFT, padx=(0, 10)) ttk.Radiobutton(plot_type_frame, text="Histogram", variable=self.plot_type_var, value="histogram", command=self.on_plot_type_change).pack(side=tk.LEFT, padx=(0, 10)) ttk.Radiobutton(plot_type_frame, text="Box Plot", variable=self.plot_type_var, value="boxplot", command=self.on_plot_type_change).pack(side=tk.LEFT) # Create plot button ttk.Button(controls_frame, text="Create Plot", command=self.create_plot).grid( row=1, column=3, columnspan=2, sticky=tk.W, padx=(20, 0), pady=(10, 0) ) # Configure grid weights controls_frame.columnconfigure(1, weight=1) controls_frame.columnconfigure(3, weight=1) def create_plot_section(self, parent): """ Create the plot display section where matplotlib figures will be shown. Args: parent: The parent widget to attach this section to """ # Plot section label ttk.Label(parent, text="Generated Plot", font=("Arial", 12, "bold")).grid( row=7, column=0, columnspan=2, sticky=tk.W, pady=(20, 10) ) # Create a frame for the plot self.plot_frame = ttk.Frame(parent) self.plot_frame.grid(row=8, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S)) # Configure grid weights for the plot frame parent.columnconfigure(0, weight=1) parent.rowconfigure(8, weight=1) self.plot_frame.columnconfigure(0, weight=1) self.plot_frame.rowconfigure(0, weight=1) def create_save_section(self, parent): """ Create the save controls section for saving plots as images. Args: parent: The parent widget to attach this section to """ # Save section label ttk.Label(parent, text="Save Plot", font=("Arial", 12, "bold")).grid( row=9, column=0, columnspan=2, sticky=tk.W, pady=(20, 10) ) # Save button ttk.Button(parent, text="Save Plot as Image", command=self.save_plot).grid( row=10, column=0, sticky=tk.W ) # Save status label self.save_status_var = tk.StringVar(value="") ttk.Label(parent, textvariable=self.save_status_var, font=("Arial", 9)).grid( row=10, column=1, sticky=tk.W, padx=(20, 0) ) def load_csv_file(self): """ Open a file dialog to select and load a CSV file. This method handles file selection and data loading with error handling. """ try: # Open file dialog for CSV selection file_path = filedialog.askopenfilename( title="Select CSV File", filetypes=[("CSV files", "*.csv"), ("All files", "*.*")] ) if file_path: # Check if a file was selected self.load_data_from_path(file_path) self.file_path_var.set(f"Loaded: {os.path.basename(file_path)}") except Exception as e: # Handle any errors during file loading messagebox.showerror("Error", f"Failed to load file: {str(e)}") def load_sample_data(self): """ Load the sample Titanic dataset included with the application. This provides users with example data to explore the visualization features. """ try: # Load the sample Titanic data sample_path = "titanic_sample.csv" if os.path.exists(sample_path): self.load_data_from_path(sample_path) self.file_path_var.set(f"Loaded: {os.path.basename(sample_path)}") else: # If sample file doesn't exist, create some dummy data self.create_dummy_data() self.file_path_var.set("Loaded: Sample Titanic Data (Generated)") except Exception as e: messagebox.showerror("Error", f"Failed to load sample data: {str(e)}") def create_dummy_data(self): """ Create dummy Titanic-like data if the sample file is not available. This ensures the app can run even without external files. """ # Generate random data similar to Titanic dataset np.random.seed(42) # For reproducible results n_passengers = 100 self.data = pd.DataFrame({ 'PassengerId': range(1, n_passengers + 1), 'Survived': np.random.choice([0, 1], n_passengers, p=[0.6, 0.4]), 'Pclass': np.random.choice([1, 2, 3], n_passengers, p=[0.2, 0.3, 0.5]), 'Sex': np.random.choice(['male', 'female'], n_passengers, p=[0.6, 0.4]), 'Age': np.random.normal(30, 15, 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(30, n_passengers), 'Embarked': np.random.choice(['S', 'C', 'Q'], n_passengers, p=[0.7, 0.2, 0.1]) }) # Update the UI with the new data self.update_ui_with_data() def load_data_from_path(self, file_path): """ Load CSV data from a specific file path. Args: file_path: Path to the CSV file to load """ # Read the CSV file using pandas self.data = pd.read_csv(file_path) # Update the UI to reflect the new data self.update_ui_with_data() def update_ui_with_data(self): """ Update the user interface after loading new data. This method populates dropdowns and displays data information. """ if self.data is not None: # Get column names for dropdowns columns = list(self.data.columns) # Update feature selection dropdowns self.x_feature_combo['values'] = columns self.y_feature_combo['values'] = columns self.color_feature_combo['values'] = columns # Set default selections if len(columns) > 0: self.x_feature_var.set(columns[0]) if len(columns) > 1: self.y_feature_var.set(columns[1]) self.color_feature_var.set(columns[0]) # Display data information self.display_data_info() # Update plot type based on selected features self.on_plot_type_change() def display_data_info(self): """ Display comprehensive information about the loaded dataset. This helps users understand the data structure and types. """ if self.data is None: self.info_text.delete(1.0, tk.END) self.info_text.insert(tk.END, "No data loaded.") return # Clear previous information self.info_text.delete(1.0, tk.END) # Create a comprehensive data summary info_lines = [] info_lines.append("DATASET OVERVIEW") info_lines.append("=" * 50) info_lines.append(f"Shape: {self.data.shape[0]} rows × {self.data.shape[1]} columns") info_lines.append(f"Memory usage: {self.data.memory_usage(deep=True).sum() / 1024:.2f} KB") info_lines.append("") info_lines.append("COLUMN INFORMATION") info_lines.append("-" * 30) # Display information for each column for col in self.data.columns: col_info = f"{col}:" # Determine data type dtype = str(self.data[col].dtype) col_info += f" {dtype}" # Add sample values for categorical data if self.data[col].dtype == 'object' or self.data[col].nunique() < 10: unique_vals = self.data[col].value_counts().head(5) col_info += f" | Sample values: {dict(unique_vals)}" else: # For numerical data, show basic statistics if pd.api.types.is_numeric_dtype(self.data[col]): col_info += f" | Range: {self.data[col].min():.2f} to {self.data[col].max():.2f}" col_info += f" | Mean: {self.data[col].mean():.2f}" info_lines.append(col_info) info_lines.append("") info_lines.append("MISSING VALUES") info_lines.append("-" * 20) missing_counts = self.data.isnull().sum() for col in self.data.columns: if missing_counts[col] > 0: info_lines.append(f"{col}: {missing_counts[col]} missing values") # Insert all information into the text widget self.info_text.insert(tk.END, "\n".join(info_lines)) def on_plot_type_change(self): """ Handle changes in plot type selection. This method enables/disables relevant controls based on the selected plot type. """ plot_type = self.plot_type_var.get() if plot_type == "scatter": # Scatter plots need both X and Y features self.y_feature_combo.config(state="readonly") self.y_feature_combo.config(foreground="black") elif plot_type == "histogram": # Histograms only need X feature self.y_feature_combo.config(state="disabled") self.y_feature_combo.config(foreground="gray") elif plot_type == "boxplot": # Box plots need both X and Y features self.y_feature_combo.config(state="readonly") self.y_feature_combo.config(foreground="black") def create_plot(self): """ Create and display the selected type of plot. This method handles different visualization types and error handling. """ if self.data is None: messagebox.showwarning("Warning", "Please load data first.") return try: # Get selected features x_feature = self.x_feature_var.get() y_feature = self.y_feature_var.get() color_feature = self.color_feature_var.get() plot_type = self.plot_type_var.get() # Validate selections if not x_feature: messagebox.showwarning("Warning", "Please select an X-axis feature.") return if plot_type == "scatter" and not y_feature: messagebox.showwarning("Warning", "Please select a Y-axis feature for scatter plot.") return # Create the plot self.generate_plot(x_feature, y_feature, color_feature, plot_type) except Exception as e: messagebox.showerror("Error", f"Failed to create plot: {str(e)}") def generate_plot(self, x_feature, y_feature, color_feature, plot_type): """ Generate the actual matplotlib plot based on user selections. Args: x_feature: Feature to use on X-axis y_feature: Feature to use on Y-axis (for scatter plots) color_feature: Feature to use for color coding plot_type: Type of plot to create """ # Clear previous plot if self.current_figure: plt.close(self.current_figure) # Create a new figure with appropriate size self.current_figure, ax = plt.subplots(figsize=(10, 6)) # Set the style for better-looking plots plt.style.use('default') sns.set_palette("husl") # Handle missing values in the data plot_data = self.data.copy() # Remove rows with missing values in the features we're plotting features_to_check = [x_feature] if y_feature: features_to_check.append(y_feature) if color_feature: features_to_check.append(color_feature) plot_data = plot_data.dropna(subset=features_to_check) if plot_data.empty: messagebox.showwarning("Warning", "No data available for plotting after removing missing values.") return # Generate the plot based on type if plot_type == "scatter": self.create_scatter_plot(ax, plot_data, x_feature, y_feature, color_feature) elif plot_type == "histogram": self.create_histogram_plot(ax, plot_data, x_feature, color_feature) elif plot_type == "boxplot": self.create_boxplot(ax, plot_data, x_feature, y_feature, color_feature) # Customize the plot appearance ax.set_title(f"{plot_type.title()} Plot: {x_feature}" + (f" vs {y_feature}" if y_feature else ""), fontsize=14, fontweight='bold') ax.set_xlabel(x_feature, fontsize=12) if y_feature: ax.set_ylabel(y_feature, fontsize=12) # Add grid for better readability ax.grid(True, alpha=0.3) # Adjust layout to prevent label cutoff plt.tight_layout() # Display the plot in the GUI self.display_plot() def create_scatter_plot(self, ax, data, x_feature, y_feature, color_feature): """ Create a scatter plot with optional color coding. Args: ax: Matplotlib axis object data: DataFrame containing the data x_feature: Feature for X-axis y_feature: Feature for Y-axis color_feature: Feature for color coding """ # Check if color feature is categorical if data[color_feature].dtype == 'object' or data[color_feature].nunique() < 10: # Create scatter plot with color coding by categorical feature unique_colors = data[color_feature].unique() colors = plt.cm.Set3(np.linspace(0, 1, len(unique_colors))) for i, color_val in enumerate(unique_colors): mask = data[color_feature] == color_val ax.scatter(data[mask][x_feature], data[mask][y_feature], c=[colors[i]], label=str(color_val), alpha=0.7, s=50) ax.legend(title=color_feature, bbox_to_anchor=(1.05, 1), loc='upper left') else: # Create scatter plot with continuous color mapping scatter = ax.scatter(data[x_feature], data[y_feature], c=data[color_feature], cmap='viridis', alpha=0.7, s=50) plt.colorbar(scatter, ax=ax, label=color_feature) def create_histogram_plot(self, ax, data, x_feature, color_feature): """ Create a histogram plot with optional color coding. Args: ax: Matplotlib axis object data: DataFrame containing the data x_feature: Feature for X-axis color_feature: Feature for color coding """ # Check if color feature is categorical if data[color_feature].dtype == 'object' or data[color_feature].nunique() < 10: # Create histogram with color coding by categorical feature unique_colors = data[color_feature].unique() colors = plt.cm.Set3(np.linspace(0, 1, len(unique_colors))) for i, color_val in enumerate(unique_colors): mask = data[color_feature] == color_val ax.hist(data[mask][x_feature], alpha=0.7, color=colors[i], label=str(color_val), bins=20) ax.legend(title=color_feature, bbox_to_anchor=(1.05, 1), loc='upper left') else: # Create simple histogram ax.hist(data[x_feature], bins=20, alpha=0.7, color='skyblue', edgecolor='black') def create_boxplot(self, ax, data, x_feature, y_feature, color_feature): """ Create a box plot with optional color coding. Args: ax: Matplotlib axis object data: DataFrame containing the data x_feature: Feature for X-axis (categorical) y_feature: Feature for Y-axis (numerical) color_feature: Feature for color coding """ # For box plots, X should be categorical and Y should be numerical if data[x_feature].dtype == 'object' or data[x_feature].nunique() < 10: # Create box plot grouped by X feature if data[color_feature].dtype == 'object' or data[color_feature].nunique() < 10: # Group by both X and color features data.boxplot(column=y_feature, by=[x_feature, color_feature], ax=ax) else: # Group only by X feature data.boxplot(column=y_feature, by=x_feature, ax=ax) else: # If X is numerical, create a simple box plot ax.boxplot(data[y_feature]) def display_plot(self): """ Display the generated matplotlib plot in the GUI. This method embeds the plot into the tkinter interface. """ # Clear previous plot display for widget in self.plot_frame.winfo_children(): widget.destroy() if self.current_figure: # Create a canvas widget to display the matplotlib figure canvas = FigureCanvasTkAgg(self.current_figure, self.plot_frame) canvas.draw() canvas.get_tk_widget().grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # Add a toolbar for plot interaction (zoom, pan, etc.) from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk toolbar = NavigationToolbar2Tk(canvas, self.plot_frame) toolbar.update() toolbar.grid(row=1, column=0, sticky=(tk.W, tk.E)) def save_plot(self): """ Save the current plot as an image file. This method allows users to save their visualizations in various formats. """ if not self.current_figure: messagebox.showwarning("Warning", "No plot to save. Please create a plot first.") return try: # Open file dialog for saving file_path = filedialog.asksaveasfilename( title="Save Plot As", defaultextension=".png", filetypes=[ ("PNG files", "*.png"), ("JPEG files", "*.jpg"), ("PDF files", "*.pdf"), ("SVG files", "*.svg"), ("All files", "*.*") ] ) if file_path: # Save the plot with high DPI for quality self.current_figure.savefig(file_path, dpi=300, bbox_inches='tight') # Update status self.save_status_var.set(f"Plot saved as: {os.path.basename(file_path)}") # Show success message messagebox.showinfo("Success", f"Plot saved successfully!\nLocation: {file_path}") except Exception as e: messagebox.showerror("Error", f"Failed to save plot: {str(e)}") self.save_status_var.set("Save failed") def main(): """ Main function to start the application. This creates the main window and starts the GUI event loop. """ # Create the main application window root = tk.Tk() # Create and initialize the CSV visualizer application app = CSVVisualizer(root) # Start the GUI event loop root.mainloop() # Entry point for the application if __name__ == "__main__": main()