Spaces:
Sleeping
Sleeping
File size: 16,389 Bytes
bbd5f9c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 | #!/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()
|