Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| Dataset Cleaning Script for Combined Crop Data | |
| This script cleans and improves the quality of the combined_crop_data.csv dataset | |
| by handling missing values, zero values, and ensuring data consistency. | |
| """ | |
| import pandas as pd | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import seaborn as sns | |
| from pathlib import Path | |
| import warnings | |
| warnings.filterwarnings('ignore') | |
| def load_and_analyze_data(file_path): | |
| """Load the dataset and perform initial analysis""" | |
| print("Loading dataset...") | |
| df = pd.read_csv(file_path) | |
| print(f"Dataset shape: {df.shape}") | |
| print(f"Columns: {list(df.columns)}") | |
| print("\nFirst few rows:") | |
| print(df.head()) | |
| print("\nDataset info:") | |
| print(df.info()) | |
| print("\nMissing values count:") | |
| missing_counts = df.isnull().sum() | |
| print(missing_counts[missing_counts > 0]) | |
| print("\nMissing values percentage:") | |
| missing_percentages = (df.isnull().sum() / len(df)) * 100 | |
| print(missing_percentages[missing_percentages > 0]) | |
| return df | |
| def analyze_zero_values(df): | |
| """Analyze zero values in numerical columns""" | |
| print("\n" + "="*50) | |
| print("ANALYZING ZERO VALUES") | |
| print("="*50) | |
| numerical_columns = ['Area', 'Production', 'Annual_Rainfall', 'Fertilizer', 'Pesticide', 'Yield'] | |
| for col in numerical_columns: | |
| if col in df.columns: | |
| zero_count = (df[col] == 0).sum() | |
| zero_percentage = (zero_count / len(df)) * 100 | |
| print(f"{col}: {zero_count} zeros ({zero_percentage:.2f}%)") | |
| if zero_count > 0: | |
| print(f" Sample rows with zero {col}:") | |
| sample_zeros = df[df[col] == 0][['Crop', 'State', 'District', col]].head(3) | |
| print(f" {sample_zeros.to_string()}") | |
| print() | |
| def clean_missing_values(df): | |
| """Handle missing values intelligently""" | |
| print("\n" + "="*50) | |
| print("CLEANING MISSING VALUES") | |
| print("="*50) | |
| df_cleaned = df.copy() | |
| # Handle missing Annual_Rainfall | |
| if 'Annual_Rainfall' in df_cleaned.columns: | |
| missing_rainfall = df_cleaned['Annual_Rainfall'].isnull().sum() | |
| print(f"Missing Annual_Rainfall values: {missing_rainfall}") | |
| if missing_rainfall > 0: | |
| # Fill with median rainfall by state and season | |
| df_cleaned['Annual_Rainfall'] = df_cleaned.groupby(['State', 'Season'])['Annual_Rainfall'].transform( | |
| lambda x: x.fillna(x.median()) | |
| ) | |
| # If still missing, fill with overall median | |
| overall_median = df_cleaned['Annual_Rainfall'].median() | |
| df_cleaned['Annual_Rainfall'].fillna(overall_median, inplace=True) | |
| print(f"Filled missing Annual_Rainfall with median values") | |
| # Handle missing Fertilizer | |
| if 'Fertilizer' in df_cleaned.columns: | |
| missing_fertilizer = df_cleaned['Fertilizer'].isnull().sum() | |
| print(f"Missing Fertilizer values: {missing_fertilizer}") | |
| if missing_fertilizer > 0: | |
| # Fill with median fertilizer by crop and state | |
| df_cleaned['Fertilizer'] = df_cleaned.groupby(['Crop', 'State'])['Fertilizer'].transform( | |
| lambda x: x.fillna(x.median()) | |
| ) | |
| # If still missing, fill with crop median | |
| df_cleaned['Fertilizer'] = df_cleaned.groupby('Crop')['Fertilizer'].transform( | |
| lambda x: x.fillna(x.median()) | |
| ) | |
| # If still missing, fill with overall median | |
| overall_median = df_cleaned['Fertilizer'].median() | |
| df_cleaned['Fertilizer'].fillna(overall_median, inplace=True) | |
| print(f"Filled missing Fertilizer with median values") | |
| # Handle missing Pesticide | |
| if 'Pesticide' in df_cleaned.columns: | |
| missing_pesticide = df_cleaned['Pesticide'].isnull().sum() | |
| print(f"Missing Pesticide values: {missing_pesticide}") | |
| if missing_pesticide > 0: | |
| # Fill with median pesticide by crop and state | |
| df_cleaned['Pesticide'] = df_cleaned.groupby(['Crop', 'State'])['Pesticide'].transform( | |
| lambda x: x.fillna(x.median()) | |
| ) | |
| # If still missing, fill with crop median | |
| df_cleaned['Pesticide'] = df_cleaned.groupby('Crop')['Pesticide'].transform( | |
| lambda x: x.fillna(x.median()) | |
| ) | |
| # If still missing, fill with overall median | |
| overall_median = df_cleaned['Pesticide'].median() | |
| df_cleaned['Pesticide'].fillna(overall_median, inplace=True) | |
| print(f"Filled missing Pesticide with median values") | |
| return df_cleaned | |
| def handle_zero_values(df): | |
| """Handle zero values appropriately""" | |
| print("\n" + "="*50) | |
| print("HANDLING ZERO VALUES") | |
| print("="*50) | |
| df_cleaned = df.copy() | |
| # For Area and Production, zero might be legitimate (no cultivation) | |
| # But we need to be careful about yield calculation | |
| # Handle problematic zero Area values where Production > 0 | |
| problematic_area = (df_cleaned['Area'] == 0) & (df_cleaned['Production'] > 0) | |
| if problematic_area.sum() > 0: | |
| print(f"Found {problematic_area.sum()} records with Area=0 but Production>0") | |
| # Calculate median area per unit production for each crop | |
| area_prod_ratio = df_cleaned[df_cleaned['Area'] > 0].groupby('Crop').apply( | |
| lambda x: (x['Area'] / x['Production']).median() | |
| ).to_dict() | |
| for idx in df_cleaned[problematic_area].index: | |
| crop = df_cleaned.loc[idx, 'Crop'] | |
| production = df_cleaned.loc[idx, 'Production'] | |
| if crop in area_prod_ratio: | |
| estimated_area = production * area_prod_ratio[crop] | |
| df_cleaned.loc[idx, 'Area'] = estimated_area | |
| print(f" Fixed Area for {crop}: estimated {estimated_area:.3f} based on production") | |
| # Handle zero Production where Area > 0 (crop failure cases) | |
| zero_production = (df_cleaned['Production'] == 0) & (df_cleaned['Area'] > 0) | |
| if zero_production.sum() > 0: | |
| print(f"Found {zero_production.sum()} records with Production=0 but Area>0 (possible crop failures)") | |
| # These might be legitimate (crop failures), so we'll keep them but ensure yield is 0 | |
| df_cleaned.loc[zero_production, 'Yield'] = 0 | |
| return df_cleaned | |
| def recalculate_yield(df): | |
| """Recalculate yield to ensure consistency""" | |
| print("\n" + "="*50) | |
| print("RECALCULATING YIELD FOR CONSISTENCY") | |
| print("="*50) | |
| df_cleaned = df.copy() | |
| # Calculate yield where both area and production are non-zero | |
| mask = (df_cleaned['Area'] > 0) & (df_cleaned['Production'] > 0) | |
| # Store original yield for comparison | |
| original_yield = df_cleaned['Yield'].copy() | |
| # Recalculate yield as Production/Area (assuming Production is in appropriate units) | |
| df_cleaned.loc[mask, 'Yield'] = df_cleaned.loc[mask, 'Production'] / df_cleaned.loc[mask, 'Area'] | |
| # For records where area or production is zero, set yield to 0 | |
| zero_mask = (df_cleaned['Area'] == 0) | (df_cleaned['Production'] == 0) | |
| df_cleaned.loc[zero_mask, 'Yield'] = 0 | |
| # Compare with original yields | |
| yield_diff = abs(df_cleaned['Yield'] - original_yield) | |
| significant_changes = yield_diff > 0.01 # More than 1% difference | |
| print(f"Yield recalculated for {mask.sum()} records") | |
| print(f"Significant changes in yield: {significant_changes.sum()} records") | |
| return df_cleaned | |
| def add_explicit_units(df): | |
| """Add explicit units to yield and production fields""" | |
| print("\n" + "="*50) | |
| print("ADDING EXPLICIT UNITS") | |
| print("="*50) | |
| df_cleaned = df.copy() | |
| # Rename columns to include units | |
| column_renames = {} | |
| if 'Yield' in df_cleaned.columns: | |
| column_renames['Yield'] = 'Yield_kg_per_hectare' | |
| print("Renamed 'Yield' to 'Yield_kg_per_hectare'") | |
| if 'Production' in df_cleaned.columns: | |
| # Assuming production is in tons (common for agricultural data) | |
| column_renames['Production'] = 'Production_tons' | |
| print("Renamed 'Production' to 'Production_tons'") | |
| if 'Area' in df_cleaned.columns: | |
| column_renames['Area'] = 'Area_hectares' | |
| print("Renamed 'Area' to 'Area_hectares'") | |
| if 'Annual_Rainfall' in df_cleaned.columns: | |
| column_renames['Annual_Rainfall'] = 'Annual_Rainfall_mm' | |
| print("Renamed 'Annual_Rainfall' to 'Annual_Rainfall_mm'") | |
| if 'Fertilizer' in df_cleaned.columns: | |
| column_renames['Fertilizer'] = 'Fertilizer_kg_per_hectare' | |
| print("Renamed 'Fertilizer' to 'Fertilizer_kg_per_hectare'") | |
| if 'Pesticide' in df_cleaned.columns: | |
| column_renames['Pesticide'] = 'Pesticide_kg_per_hectare' | |
| print("Renamed 'Pesticide' to 'Pesticide_kg_per_hectare'") | |
| df_cleaned.rename(columns=column_renames, inplace=True) | |
| return df_cleaned | |
| def detect_and_handle_outliers(df): | |
| """Detect and handle outliers in numerical columns""" | |
| print("\n" + "="*50) | |
| print("DETECTING AND HANDLING OUTLIERS") | |
| print("="*50) | |
| df_cleaned = df.copy() | |
| numerical_cols = df_cleaned.select_dtypes(include=[np.number]).columns | |
| outliers_summary = {} | |
| for col in numerical_cols: | |
| if col not in ['Crop_Year']: # Skip year column | |
| Q1 = df_cleaned[col].quantile(0.25) | |
| Q3 = df_cleaned[col].quantile(0.75) | |
| IQR = Q3 - Q1 | |
| lower_bound = Q1 - 1.5 * IQR | |
| upper_bound = Q3 + 1.5 * IQR | |
| outliers = (df_cleaned[col] < lower_bound) | (df_cleaned[col] > upper_bound) | |
| outliers_count = outliers.sum() | |
| if outliers_count > 0: | |
| outliers_summary[col] = { | |
| 'count': outliers_count, | |
| 'percentage': (outliers_count / len(df_cleaned)) * 100, | |
| 'lower_bound': lower_bound, | |
| 'upper_bound': upper_bound | |
| } | |
| # For extreme outliers, cap them at reasonable bounds | |
| extreme_outliers = (df_cleaned[col] > upper_bound + 2 * IQR) | (df_cleaned[col] < lower_bound - 2 * IQR) | |
| if extreme_outliers.sum() > 0: | |
| print(f"Capping {extreme_outliers.sum()} extreme outliers in {col}") | |
| df_cleaned.loc[df_cleaned[col] > upper_bound + 2 * IQR, col] = upper_bound | |
| df_cleaned.loc[df_cleaned[col] < lower_bound - 2 * IQR, col] = max(0, lower_bound) | |
| print("\nOutliers summary:") | |
| for col, info in outliers_summary.items(): | |
| print(f"{col}: {info['count']} outliers ({info['percentage']:.2f}%)") | |
| return df_cleaned | |
| def ensure_data_consistency(df): | |
| """Ensure logical consistency between related fields""" | |
| print("\n" + "="*50) | |
| print("ENSURING DATA CONSISTENCY") | |
| print("="*50) | |
| df_cleaned = df.copy() | |
| # Ensure consistent data types | |
| if 'Crop_Year' in df_cleaned.columns: | |
| df_cleaned['Crop_Year'] = df_cleaned['Crop_Year'].astype(int) | |
| # Ensure non-negative values for physical quantities | |
| numerical_cols = df_cleaned.select_dtypes(include=[np.number]).columns | |
| for col in numerical_cols: | |
| if col != 'Crop_Year': | |
| negative_count = (df_cleaned[col] < 0).sum() | |
| if negative_count > 0: | |
| print(f"Found {negative_count} negative values in {col}, setting to 0") | |
| df_cleaned.loc[df_cleaned[col] < 0, col] = 0 | |
| # Standardize text fields | |
| text_columns = ['Crop', 'Season', 'State', 'District'] | |
| for col in text_columns: | |
| if col in df_cleaned.columns: | |
| df_cleaned[col] = df_cleaned[col].str.strip() # Remove whitespace | |
| df_cleaned[col] = df_cleaned[col].str.title() # Standardize capitalization | |
| return df_cleaned | |
| def generate_data_quality_report(original_df, cleaned_df): | |
| """Generate a comprehensive data quality report""" | |
| print("\n" + "="*60) | |
| print("DATA QUALITY IMPROVEMENT REPORT") | |
| print("="*60) | |
| print(f"\nDataset size: {len(cleaned_df)} records") | |
| # Missing values comparison | |
| print("\nMissing Values - Before vs After:") | |
| print("-" * 40) | |
| for col in original_df.columns: | |
| original_missing = original_df[col].isnull().sum() | |
| cleaned_missing = cleaned_df[col].isnull().sum() if col in cleaned_df.columns else 0 | |
| if original_missing > 0 or cleaned_missing > 0: | |
| print(f"{col:20} {original_missing:8} → {cleaned_missing:8}") | |
| # Zero values comparison (for numerical columns) | |
| print("\nZero Values - Before vs After:") | |
| print("-" * 40) | |
| numerical_cols = ['Area', 'Production', 'Annual_Rainfall', 'Fertilizer', 'Pesticide', 'Yield'] | |
| for col in numerical_cols: | |
| if col in original_df.columns: | |
| original_zeros = (original_df[col] == 0).sum() | |
| # Find corresponding column in cleaned dataset | |
| cleaned_col = None | |
| for cleaned_column in cleaned_df.columns: | |
| if col.lower() in cleaned_column.lower(): | |
| cleaned_col = cleaned_column | |
| break | |
| if cleaned_col: | |
| cleaned_zeros = (cleaned_df[cleaned_col] == 0).sum() | |
| print(f"{col:20} {original_zeros:8} → {cleaned_zeros:8}") | |
| # Data quality metrics | |
| print("\nData Quality Improvements:") | |
| print("-" * 40) | |
| # Completeness | |
| original_completeness = (1 - original_df.isnull().sum().sum() / (len(original_df) * len(original_df.columns))) * 100 | |
| cleaned_completeness = (1 - cleaned_df.isnull().sum().sum() / (len(cleaned_df) * len(cleaned_df.columns))) * 100 | |
| print(f"Completeness: {original_completeness:.2f}% → {cleaned_completeness:.2f}%") | |
| # Column names with units | |
| print("\nColumns with explicit units:") | |
| print("-" * 40) | |
| for col in cleaned_df.columns: | |
| if any(unit in col.lower() for unit in ['kg', 'hectare', 'tons', 'mm']): | |
| print(f"✓ {col}") | |
| return cleaned_df | |
| def save_cleaned_dataset(df, output_path): | |
| """Save the cleaned dataset""" | |
| print(f"\nSaving cleaned dataset to: {output_path}") | |
| df.to_csv(output_path, index=False) | |
| print(f"✓ Cleaned dataset saved with {len(df)} records and {len(df.columns)} columns") | |
| # Also save a sample for verification | |
| sample_path = output_path.replace('.csv', '_sample.csv') | |
| df.head(1000).to_csv(sample_path, index=False) | |
| print(f"✓ Sample dataset saved to: {sample_path}") | |
| def main(): | |
| """Main function to orchestrate the data cleaning process""" | |
| print("="*60) | |
| print("CROP DATA CLEANING AND QUALITY IMPROVEMENT") | |
| print("="*60) | |
| # File paths | |
| input_file = Path("/home/aiavid/Yeild_pred_SIH/data/combined_crop_data.csv") | |
| output_file = Path("/home/aiavid/Yeild_pred_SIH/data/combined_crop_data_cleaned.csv") | |
| # Load and analyze original data | |
| original_df = load_and_analyze_data(input_file) | |
| # Analyze issues | |
| analyze_zero_values(original_df) | |
| # Start cleaning process | |
| print("\n" + "="*60) | |
| print("STARTING DATA CLEANING PROCESS") | |
| print("="*60) | |
| # Step 1: Handle missing values | |
| cleaned_df = clean_missing_values(original_df) | |
| # Step 2: Handle zero values | |
| cleaned_df = handle_zero_values(cleaned_df) | |
| # Step 3: Recalculate yield for consistency | |
| cleaned_df = recalculate_yield(cleaned_df) | |
| # Step 4: Add explicit units | |
| cleaned_df = add_explicit_units(cleaned_df) | |
| # Step 5: Handle outliers | |
| cleaned_df = detect_and_handle_outliers(cleaned_df) | |
| # Step 6: Ensure data consistency | |
| cleaned_df = ensure_data_consistency(cleaned_df) | |
| # Generate quality report | |
| cleaned_df = generate_data_quality_report(original_df, cleaned_df) | |
| # Save cleaned dataset | |
| save_cleaned_dataset(cleaned_df, output_file) | |
| print("\n" + "="*60) | |
| print("DATA CLEANING COMPLETED SUCCESSFULLY!") | |
| print("="*60) | |
| return cleaned_df | |
| if __name__ == "__main__": | |
| main() | |