CoolFace
Apppublic

vashu2425/Data_Analysis_And_Feature_Engineering_Platform

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
eda_analysis.py480 linesDownload Raw Back to root
1"""2EDA Analysis Module3 4This module handles all dataset processing and analysis, providing structured information5about the dataset that can be used for visualization and LLM prompting.6"""7 8import pandas as pd9import numpy as np10from typing import Dict, List, Tuple, Any, Optional11import matplotlib.pyplot as plt12import seaborn as sns13from sklearn.preprocessing import StandardScaler14from io import BytesIO15import base6416 17class DatasetAnalyzer:18    """Class for analyzing datasets and extracting key information"""19    20    def __init__(self, df: pd.DataFrame = None):21        """Initialize with an optional dataframe"""22        self.df = df23        self.analysis_results = {}24    25    def load_dataframe(self, df: pd.DataFrame) -> None:26        """Load a dataframe for analysis"""27        self.df = df28        # Reset analysis results when loading a new dataframe29        self.analysis_results = {}30    31    def analyze_dataset(self) -> Dict[str, Any]:32        """33        Perform comprehensive analysis on the dataset34        35        Returns:36            Dict: Dictionary containing all analysis results37        """38        if self.df is None:39            raise ValueError("No dataframe loaded. Please load a dataframe first.")40        41        # Basic information42        self.analysis_results["shape"] = self.df.shape43        self.analysis_results["columns"] = list(self.df.columns)44        self.analysis_results["dtypes"] = {col: str(self.df[col].dtype) for col in self.df.columns}45        46        # Missing values47        self.analysis_results["missing_values"] = self._analyze_missing_values()48        49        # Basic statistics50        self.analysis_results["basic_stats"] = self._generate_basic_stats()51        52        # Correlations (for numerical columns)53        self.analysis_results["correlations"] = self._analyze_correlations()54        55        # Sample data56        self.analysis_results["sample_data"] = self.df.head().to_string()57        58        # Additional analyses59        self.analysis_results["categorical_columns"] = self._identify_categorical_columns()60        self.analysis_results["numerical_columns"] = self._identify_numerical_columns()61        self.analysis_results["unique_values"] = self._count_unique_values()62        63        return self.analysis_results64    65    def _analyze_missing_values(self) -> Dict[str, Tuple[int, float]]:66        """67        Analyze missing values in the dataset68        69        Returns:70            Dict: Column names as keys, tuples of (count, percentage) as values71        """72        missing_values = {}73        for col in self.df.columns:74            count = self.df[col].isna().sum()75            percentage = round((count / len(self.df)) * 100, 2)76            missing_values[col] = (count, percentage)77        78        return missing_values79    80    def _generate_basic_stats(self) -> str:81        """82        Generate basic statistics for the dataset83        84        Returns:85            str: String representation of basic statistics86        """87        # For numerical columns88        num_stats = self.df.describe().to_string()89        90        # For categorical columns91        cat_columns = self._identify_categorical_columns()92        cat_stats = ""93        if cat_columns:94            cat_stats = "\n\nCategorical columns statistics:\n"95            for col in cat_columns:96                value_counts = self.df[col].value_counts().head(10)97                cat_stats += f"\n{col} - Top values:\n{value_counts.to_string()}\n"98        99        return num_stats + cat_stats100    101    def _analyze_correlations(self) -> str:102        """103        Analyze correlations between numerical features104        105        Returns:106            str: String representation of top correlations107        """108        num_columns = self._identify_numerical_columns()109        110        if not num_columns or len(num_columns) < 2:111            return "Not enough numerical columns for correlation analysis."112        113        corr_matrix = self.df[num_columns].corr()114        115        # Get top correlations (excluding self-correlations)116        corr_pairs = []117        for i in range(len(num_columns)):118            for j in range(i+1, len(num_columns)):119                col1, col2 = num_columns[i], num_columns[j]120                corr_value = corr_matrix.loc[col1, col2]121                if not np.isnan(corr_value):122                    corr_pairs.append((col1, col2, corr_value))123        124        # Sort by absolute correlation value125        corr_pairs.sort(key=lambda x: abs(x[2]), reverse=True)126        127        # Format results128        result = "Top correlations:\n"129        for col1, col2, corr in corr_pairs[:10]:  # Top 10 correlations130            result += f"{col1} -- {col2}: {corr:.4f}\n"131        132        return result133    134    def _identify_categorical_columns(self) -> List[str]:135        """136        Identify categorical columns in the dataset137        138        Returns:139            List[str]: List of categorical column names140        """141        cat_columns = []142        for col in self.df.columns:143            # Consider object, category, and boolean types as categorical144            if self.df[col].dtype == 'object' or self.df[col].dtype == 'category' or self.df[col].dtype == 'bool':145                cat_columns.append(col)146            # Also consider int/float columns with few unique values as categorical147            elif (self.df[col].dtype == 'int64' or self.df[col].dtype == 'float64') and \148                 self.df[col].nunique() < 10 and self.df[col].nunique() / len(self.df) < 0.05:149                cat_columns.append(col)150        151        return cat_columns152    153    def _identify_numerical_columns(self) -> List[str]:154        """155        Identify numerical columns in the dataset156        157        Returns:158            List[str]: List of numerical column names159        """160        num_columns = []161        cat_columns = self._identify_categorical_columns()162        163        for col in self.df.columns:164            if col not in cat_columns and pd.api.types.is_numeric_dtype(self.df[col].dtype):165                num_columns.append(col)166        167        return num_columns168    169    def _count_unique_values(self) -> Dict[str, int]:170        """171        Count unique values for each column172        173        Returns:174            Dict: Column names as keys, unique count as values175        """176        return {col: self.df[col].nunique() for col in self.df.columns}177    178    def generate_eda_visualizations(self) -> Dict[str, str]:179        """180        Generate common EDA visualizations181        182        Returns:183            Dict: Dictionary of visualization titles and their base64-encoded images184        """185        if self.df is None:186            raise ValueError("No dataframe loaded. Please load a dataframe first.")187        188        visualizations = {}189        190        # 1. Missing values heatmap191        visualizations["missing_values_heatmap"] = self._plot_missing_values()192        193        # 2. Distribution plots for numerical features194        num_columns = self._identify_numerical_columns()195        for i, col in enumerate(num_columns[:5]):  # Limit to first 5 numerical columns196            visualizations[f"distribution_{col}"] = self._plot_distribution(col)197        198        # 3. Correlation heatmap199        visualizations["correlation_heatmap"] = self._plot_correlation_heatmap()200        201        # 4. Categorical feature distributions202        cat_columns = self._identify_categorical_columns()203        for i, col in enumerate(cat_columns[:5]):  # Limit to first 5 categorical columns204            visualizations[f"categorical_{col}"] = self._plot_categorical_distribution(col)205        206        # 5. Scatter plot of 2 most correlated features207        if len(num_columns) >= 2:208            visualizations["scatter_plot"] = self._plot_scatter_correlation()209        210        return visualizations211    212    def _plot_missing_values(self) -> str:213        """Generate missing values heatmap"""214        plt.figure(figsize=(10, 6))215        sns.heatmap(self.df.isnull(), cmap='viridis', yticklabels=False, cbar=True, cbar_kws={'label': 'Missing Data'})216        plt.tight_layout()217        plt.title('Missing Values Heatmap')218        219        # Convert plot to base64 string220        return self._fig_to_base64(plt.gcf())221    222    def _plot_distribution(self, column: str) -> str:223        """Generate distribution plot for a numerical column"""224        plt.figure(figsize=(10, 6))225        226        # Histogram with KDE227        sns.histplot(data=self.df, x=column, kde=True)228        229        plt.title(f'Distribution of {column}')230        plt.xlabel(column)231        plt.ylabel('Frequency')232        plt.tight_layout()233        234        # Convert plot to base64 string235        return self._fig_to_base64(plt.gcf())236    237    def _plot_correlation_heatmap(self) -> str:238        """Generate correlation heatmap"""239        num_columns = self._identify_numerical_columns()240        241        if not num_columns or len(num_columns) < 2:242            return ""243        244        plt.figure(figsize=(12, 10))245        corr_matrix = self.df[num_columns].corr()246        mask = np.triu(np.ones_like(corr_matrix, dtype=bool))247        248        # Custom diverging palette249        cmap = sns.diverging_palette(230, 20, as_cmap=True)250        251        # Draw heatmap252        sns.heatmap(corr_matrix, mask=mask, cmap=cmap, vmax=1, vmin=-1, center=0,253                    square=True, linewidths=.5, annot=True, fmt=".2f")254        255        plt.title('Correlation Heatmap')256        plt.tight_layout()257        258        # Convert plot to base64 string259        return self._fig_to_base64(plt.gcf())260    261    def _plot_categorical_distribution(self, column: str) -> str:262        """Generate bar plot for categorical column"""263        plt.figure(figsize=(10, 6))264        265        # Get value counts and limit to top 10 categories if there are too many266        value_counts = self.df[column].value_counts()267        if len(value_counts) > 10:268            # Keep top 9 categories and group the rest as 'Other'269            top_categories = value_counts.nlargest(9).index270            data = self.df.copy()271            data[column] = data[column].apply(lambda x: x if x in top_categories else 'Other')272            sns.countplot(y=column, data=data, order=data[column].value_counts().index)273        else:274            sns.countplot(y=column, data=self.df, order=value_counts.index)275        276        plt.title(f'Distribution of {column}')277        plt.xlabel('Count')278        plt.ylabel(column)279        plt.tight_layout()280        281        # Convert plot to base64 string282        return self._fig_to_base64(plt.gcf())283    284    def _plot_scatter_correlation(self) -> str:285        """Generate scatter plot of two most correlated features"""286        num_columns = self._identify_numerical_columns()287        288        if not num_columns or len(num_columns) < 2:289            return ""290        291        # Find the two most correlated features292        corr_matrix = self.df[num_columns].corr().abs()293        294        # Get upper triangle mask295        mask = np.triu(np.ones_like(corr_matrix, dtype=bool))296        corr_matrix = corr_matrix.mask(mask)297        298        # Find the max correlation299        max_corr = corr_matrix.max().max()300        max_corr_idx = corr_matrix.stack().idxmax()301        302        if pd.isna(max_corr):303            return ""304        305        # Get the column names306        col1, col2 = max_corr_idx307        308        # Create scatter plot309        plt.figure(figsize=(10, 6))310        311        # Add regression line312        sns.regplot(x=col1, y=col2, data=self.df, scatter_kws={'alpha': 0.5})313        314        plt.title(f'Scatter plot of {col1} vs {col2} (correlation: {corr_matrix.loc[col1, col2]:.2f})')315        plt.tight_layout()316        317        # Convert plot to base64 string318        return self._fig_to_base64(plt.gcf())319    320    def _fig_to_base64(self, fig) -> str:321        """Convert matplotlib figure to base64 string"""322        buf = BytesIO()323        fig.savefig(buf, format='png', bbox_inches='tight')324        buf.seek(0)325        img_str = base64.b64encode(buf.read()).decode('utf-8')326        plt.close(fig)327        return img_str328    329    def suggest_data_preprocessing(self) -> Dict[str, List[str]]:330        """331        Suggest preprocessing steps based on dataset analysis332        333        Returns:334            Dict: Dictionary of preprocessing suggestions for each column type335        """336        if not self.analysis_results:337            self.analyze_dataset()338        339        suggestions = {340            "numerical": [],341            "categorical": [],342            "missing_values": [],343            "outliers": [],344            "general": []345        }346        347        # Missing values suggestions348        missing_cols = [col for col, (count, _) in self.analysis_results["missing_values"].items() if count > 0]349        if missing_cols:350            suggestions["missing_values"].append(f"Found {len(missing_cols)} columns with missing values.")351            if len(missing_cols) > 5:352                suggestions["missing_values"].append(f"Columns with highest missing values: {', '.join(missing_cols[:5])}...")353            else:354                suggestions["missing_values"].append(f"Columns with missing values: {', '.join(missing_cols)}")355            356            suggestions["missing_values"].append("Consider these strategies for handling missing values:")357            suggestions["missing_values"].append("- Imputation (mean/median for numerical, mode for categorical)")358            suggestions["missing_values"].append("- Creating missing value indicators as new features")359            suggestions["missing_values"].append("- Removing rows or columns with too many missing values")360        361        # Numerical column suggestions362        num_cols = self.analysis_results["numerical_columns"]363        if num_cols:364            suggestions["numerical"].append(f"Found {len(num_cols)} numerical columns.")365            suggestions["numerical"].append("Consider these preprocessing steps:")366            suggestions["numerical"].append("- Scaling (StandardScaler or MinMaxScaler)")367            suggestions["numerical"].append("- Check for skewness and apply log or Box-Cox transformation if needed")368            suggestions["numerical"].append("- Create binned versions of continuous variables")369            370            # Check for potential outliers371            for col in num_cols:372                if col in self.df.columns:  # Safety check373                    q1 = self.df[col].quantile(0.25)374                    q3 = self.df[col].quantile(0.75)375                    iqr = q3 - q1376                    outlier_count = ((self.df[col] < (q1 - 1.5 * iqr)) | (self.df[col] > (q3 + 1.5 * iqr))).sum()377                    378                    if outlier_count > 0:379                        percentage = round((outlier_count / len(self.df)) * 100, 2)380                        if percentage > 5:  # If more than 5% are outliers381                            suggestions["outliers"].append(f"Column '{col}' has {outlier_count} potential outliers ({percentage}%).")382        383        # Categorical column suggestions384        cat_cols = self.analysis_results["categorical_columns"]385        if cat_cols:386            suggestions["categorical"].append(f"Found {len(cat_cols)} categorical columns.")387            388            # Check cardinality (number of unique values)389            high_cardinality = []390            for col in cat_cols:391                unique_count = self.analysis_results["unique_values"].get(col, 0)392                if unique_count > 10:393                    high_cardinality.append((col, unique_count))394            395            if high_cardinality:396                suggestions["categorical"].append("High cardinality columns (many unique values):")397                for col, count in sorted(high_cardinality, key=lambda x: x[1], reverse=True)[:5]:398                    suggestions["categorical"].append(f"- {col}: {count} unique values")399                400                suggestions["categorical"].append("For high cardinality columns, consider:")401                suggestions["categorical"].append("- Grouping less frequent categories")402                suggestions["categorical"].append("- Target encoding or embedding techniques")403            404            suggestions["categorical"].append("General categorical encoding strategies:")405            suggestions["categorical"].append("- One-hot encoding for low cardinality columns")406            suggestions["categorical"].append("- Label encoding for ordinal variables")407        408        # General suggestions409        suggestions["general"].append("General preprocessing recommendations:")410        suggestions["general"].append("- Check for duplicate rows and remove if necessary")411        suggestions["general"].append("- Normalize text fields (lowercase, remove special characters)")412        suggestions["general"].append("- Create feature interactions for highly correlated features")413        414        return suggestions415        416    def generate_feature_engineering_ideas(self) -> List[str]:417        """418        Generate feature engineering ideas based on dataset analysis419        420        Returns:421            List[str]: List of feature engineering suggestions422        """423        if not self.analysis_results:424            self.analyze_dataset()425        426        ideas = []427        428        # Get column types429        num_cols = self.analysis_results["numerical_columns"]430        cat_cols = self.analysis_results["categorical_columns"]431        432        # Aggregation features433        if len(num_cols) >= 2:434            ideas.append("### Numerical Feature Transformations:")435            ideas.append("1. Create polynomial features for continuous variables")436            ideas.append("2. Apply mathematical transformations (log, sqrt, square) to handle skewed distributions")437            ideas.append("3. Create binned versions of continuous features to capture non-linear relationships")438            439            # Check for date/time related column names440            time_related_cols = [col for col in self.df.columns if any(x in col.lower() for x in ['date', 'time', 'year', 'month', 'day'])]441            if time_related_cols:442                ideas.append("\n### Time-Based Features:")443                ideas.append(f"Detected potential date/time columns: {', '.join(time_related_cols)}")444                ideas.append("1. Extract components like year, month, day, weekday, quarter")445                ideas.append("2. Create cyclical features using sine/cosine transformations for periodic time components")446                ideas.append("3. Calculate time since specific events or time differences between dates")447        448        # Categorical interactions449        if len(cat_cols) >= 2:450            ideas.append("\n### Categorical Feature Engineering:")451            ideas.append("1. Create interaction features by combining categorical variables")452            ideas.append("2. Use target encoding for high cardinality categorical features")453            ideas.append("3. Combine rare categories into an 'Other' category to reduce dimensionality")454        455        # Mixed interactions456        if num_cols and cat_cols:457            ideas.append("\n### Feature Interactions:")458            ideas.append("1. Create group-based statistics (mean, median, min, max) of numerical features grouped by categorical features")459            ideas.append("2. Calculate the difference from group means for numerical features")460            ideas.append("3. Create ratio or difference features between related numerical columns")461        462        # Dimensionality reduction463        if len(num_cols) > 10:464            ideas.append("\n### Dimensionality Reduction:")465            ideas.append("1. Apply PCA to reduce dimensionality and create principal components")466            ideas.append("2. Use feature selection methods (information gain, chi-square, mutual information)")467            ideas.append("3. Try UMAP or t-SNE for non-linear dimensionality reduction")468        469        # Text features470        text_cols = [col for col in self.df.columns if self.df[col].dtype == 'object' and471                     self.df[col].apply(lambda x: isinstance(x, str) and len(x.split()) > 3).mean() > 0.5]472        if text_cols:473            ideas.append("\n### Text Feature Engineering:")474            ideas.append(f"Detected potential text columns: {', '.join(text_cols)}")475            ideas.append("1. Create bag-of-words or TF-IDF representations")476            ideas.append("2. Extract text length, word count, and other statistical features")477            ideas.append("3. Consider pretrained word embeddings or sentence transformers")478        479        return ideas480