Text Classification
Transformers
Safetensors
yield-weather-soil
crop-yield
multi-temporal
regression
yield-estimation
custom_code
Instructions to use ICICLE-AI/yield-estimation with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use ICICLE-AI/yield-estimation with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="ICICLE-AI/yield-estimation", trust_remote_code=True)# Load model directly from transformers import AutoModelForSequenceClassification model = AutoModelForSequenceClassification.from_pretrained("ICICLE-AI/yield-estimation", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
| from pathlib import Path | |
| import numpy as np | |
| import pandas as pd | |
| # ============================================================ | |
| # Configuration | |
| # ============================================================ | |
| INPUT_CSV = Path("khaki_multi_crop_yield.csv") | |
| OUTPUT_DIR = Path("data/cornbelt") | |
| TRAIN_YEARS = [2013, 2014, 2015, 2016] | |
| VAL_YEAR = 2017 | |
| TEST_YEAR = 2018 | |
| # Public dataset weather mapping | |
| WEATHER_MAP = { | |
| "prcp": 1, | |
| "srad": 2, | |
| "swe": 3, | |
| "tmax": 4, | |
| "tmin": 5, | |
| "vp": 6, | |
| } | |
| SOIL_MEASUREMENTS = [ | |
| "bdod", | |
| "cec", | |
| "cfvo", | |
| "clay", | |
| "nitrogen", | |
| "ocd", | |
| "ocs", | |
| "phh2o", | |
| "sand", | |
| "silt", | |
| "soc", | |
| ] | |
| SOIL_DEPTHS = [ | |
| "0-5cm", | |
| "5-15cm", | |
| "15-30cm", | |
| "30-60cm", | |
| "60-100cm", | |
| "100-200cm", | |
| ] | |
| SOIL_VARS = [ | |
| f"{measurement}_mean_{depth}" | |
| for measurement in SOIL_MEASUREMENTS | |
| for depth in SOIL_DEPTHS | |
| ] | |
| # ============================================================ | |
| # Load | |
| # ============================================================ | |
| print(f"Reading {INPUT_CSV}") | |
| df = pd.read_csv(INPUT_CSV) | |
| print("Raw shape:", df.shape) | |
| # ============================================================ | |
| # Verify required columns | |
| # ============================================================ | |
| if "corn_yield" not in df.columns: | |
| raise ValueError("corn_yield column not found") | |
| required_meta = [ | |
| "loc_ID", | |
| "year", | |
| "State", | |
| "County", | |
| "lat", | |
| "lng", | |
| ] | |
| missing_meta = [ | |
| c for c in required_meta | |
| if c not in df.columns | |
| ] | |
| if missing_meta: | |
| raise ValueError( | |
| f"Missing metadata columns: {missing_meta}" | |
| ) | |
| missing_soil = [ | |
| c for c in SOIL_VARS | |
| if c not in df.columns | |
| ] | |
| if missing_soil: | |
| raise ValueError( | |
| f"Missing soil columns: {missing_soil}" | |
| ) | |
| # Verify all 52 weekly values exist for each weather variable | |
| missing_weather = [] | |
| for weather_name, source_idx in WEATHER_MAP.items(): | |
| for week in range(1, 53): | |
| src = f"W_{source_idx}_{week}" | |
| if src not in df.columns: | |
| missing_weather.append(src) | |
| if missing_weather: | |
| raise ValueError( | |
| f"Missing weather columns: {missing_weather[:20]}" | |
| ) | |
| # ============================================================ | |
| # Convert year and corn yield to numeric | |
| # ============================================================ | |
| df["year"] = pd.to_numeric( | |
| df["year"], | |
| errors="coerce", | |
| ) | |
| df["corn_yield"] = pd.to_numeric( | |
| df["corn_yield"], | |
| errors="coerce", | |
| ) | |
| # ============================================================ | |
| # IMPORTANT: | |
| # Filter to corn samples from 2013-2018 BEFORE constructing out | |
| # ============================================================ | |
| df = df[ | |
| (df["year"] >= 2013) & | |
| (df["year"] <= 2018) | |
| ].copy() | |
| # Remove samples without valid corn yield or year | |
| df = df.dropna( | |
| subset=[ | |
| "corn_yield", | |
| "year", | |
| ] | |
| ).reset_index(drop=True) | |
| df["year"] = df["year"].astype(int) | |
| print() | |
| print("===================================") | |
| print("FILTERED SOURCE DATA") | |
| print("===================================") | |
| print( | |
| "Years retained:", | |
| sorted(df["year"].unique()) | |
| ) | |
| print( | |
| "Rows after 2013-2018 + corn yield filter:", | |
| len(df) | |
| ) | |
| print("\nSamples per year:") | |
| print( | |
| df["year"] | |
| .value_counts() | |
| .sort_index() | |
| ) | |
| expected_years = { | |
| 2013, | |
| 2014, | |
| 2015, | |
| 2016, | |
| 2017, | |
| 2018, | |
| } | |
| assert set(df["year"].unique()) == expected_years | |
| # ============================================================ | |
| # Construct metadata block | |
| # ============================================================ | |
| metadata_df = pd.DataFrame( | |
| { | |
| "crop": "corn", | |
| # County/location acts as the sample spatial identifier. | |
| # It is metadata required by the current dataset interface. | |
| "farm_field": ( | |
| "county_" + | |
| df["loc_ID"].astype(str) | |
| ), | |
| "year": df["year"].astype(int), | |
| "yield": df[ | |
| "corn_yield" | |
| ].astype(np.float32), | |
| "loc_ID": df["loc_ID"], | |
| "state": df[ | |
| "State" | |
| ].astype(str), | |
| "county": df[ | |
| "County" | |
| ].astype(str), | |
| "lat": pd.to_numeric( | |
| df["lat"], | |
| errors="coerce", | |
| ).astype(np.float32), | |
| "lng": pd.to_numeric( | |
| df["lng"], | |
| errors="coerce", | |
| ).astype(np.float32), | |
| } | |
| ) | |
| # ============================================================ | |
| # Weather block | |
| # | |
| # IMPORTANT: | |
| # - No interpolation | |
| # - No daily conversion | |
| # - No aggregation | |
| # | |
| # Each source W_x_1 ... W_x_52 is copied directly. | |
| # | |
| # Example: | |
| # W_1_1 -> prcp_0 | |
| # W_1_2 -> prcp_1 | |
| # ... | |
| # W_1_52 -> prcp_51 | |
| # ============================================================ | |
| weather_data = {} | |
| for weather_name, source_idx in WEATHER_MAP.items(): | |
| for week in range(1, 53): | |
| src = f"W_{source_idx}_{week}" | |
| # Zero-based temporal indexing used by current loader | |
| dst = f"{weather_name}_{week - 1}" | |
| weather_data[dst] = pd.to_numeric( | |
| df[src], | |
| errors="coerce", | |
| ).astype(np.float32) | |
| weather_df = pd.DataFrame( | |
| weather_data, | |
| index=df.index, | |
| ) | |
| # ============================================================ | |
| # Soil block | |
| # ============================================================ | |
| soil_data = {} | |
| for col in SOIL_VARS: | |
| soil_data[col] = pd.to_numeric( | |
| df[col], | |
| errors="coerce", | |
| ).astype(np.float32) | |
| soil_df = pd.DataFrame( | |
| soil_data, | |
| index=df.index, | |
| ) | |
| # ============================================================ | |
| # Combine all blocks | |
| # ============================================================ | |
| out = pd.concat( | |
| [ | |
| metadata_df.reset_index(drop=True), | |
| weather_df.reset_index(drop=True), | |
| soil_df.reset_index(drop=True), | |
| ], | |
| axis=1, | |
| ) | |
| # Replace inf values with NaN. | |
| # Normalization / missing-value handling remains in train_hf.py | |
| # and YieldDataset. | |
| out = out.replace( | |
| [np.inf, -np.inf], | |
| np.nan, | |
| ) | |
| # ============================================================ | |
| # Critical safety check | |
| # ============================================================ | |
| print() | |
| print("===================================") | |
| print("FINAL MODEL DATASET") | |
| print("===================================") | |
| print( | |
| "Shape:", | |
| out.shape, | |
| ) | |
| print( | |
| "Years:", | |
| sorted(out["year"].unique()) | |
| ) | |
| print( | |
| "Rows:", | |
| len(out) | |
| ) | |
| assert set(out["year"].unique()) == { | |
| 2013, | |
| 2014, | |
| 2015, | |
| 2016, | |
| 2017, | |
| 2018, | |
| } | |
| assert len(out) == len(df) | |
| # ============================================================ | |
| # Chronological split | |
| # | |
| # Train: 2013-2016 | |
| # Val: 2017 | |
| # Test: 2018 | |
| # ============================================================ | |
| train_df = out[ | |
| out["year"].isin(TRAIN_YEARS) | |
| ].reset_index(drop=True) | |
| val_df = out[ | |
| out["year"] == VAL_YEAR | |
| ].reset_index(drop=True) | |
| test_df = out[ | |
| out["year"] == TEST_YEAR | |
| ].reset_index(drop=True) | |
| # ============================================================ | |
| # Split reporting | |
| # ============================================================ | |
| print() | |
| print("===================================") | |
| print("SPLITS") | |
| print("===================================") | |
| print("\nTRAIN") | |
| print( | |
| train_df[ | |
| "year" | |
| ].value_counts().sort_index() | |
| ) | |
| print( | |
| "Samples:", | |
| len(train_df), | |
| ) | |
| print( | |
| "Counties:", | |
| train_df[ | |
| "farm_field" | |
| ].nunique(), | |
| ) | |
| print("\nVALIDATION") | |
| print( | |
| val_df[ | |
| "year" | |
| ].value_counts().sort_index() | |
| ) | |
| print( | |
| "Samples:", | |
| len(val_df), | |
| ) | |
| print( | |
| "Counties:", | |
| val_df[ | |
| "farm_field" | |
| ].nunique(), | |
| ) | |
| print("\nTEST") | |
| print( | |
| test_df[ | |
| "year" | |
| ].value_counts().sort_index() | |
| ) | |
| print( | |
| "Samples:", | |
| len(test_df), | |
| ) | |
| print( | |
| "Counties:", | |
| test_df[ | |
| "farm_field" | |
| ].nunique(), | |
| ) | |
| # ============================================================ | |
| # Split sanity checks | |
| # ============================================================ | |
| assert set( | |
| train_df["year"].unique() | |
| ) == { | |
| 2013, | |
| 2014, | |
| 2015, | |
| 2016, | |
| } | |
| assert set( | |
| val_df["year"].unique() | |
| ) == { | |
| 2017, | |
| } | |
| assert set( | |
| test_df["year"].unique() | |
| ) == { | |
| 2018, | |
| } | |
| assert ( | |
| len(train_df) | |
| + len(val_df) | |
| + len(test_df) | |
| == len(out) | |
| ) | |
| # No row should appear in more than one split | |
| assert set(train_df.index).isdisjoint( | |
| set(range( | |
| len(train_df), | |
| len(train_df) + len(val_df) | |
| )) | |
| ) | |
| print() | |
| print("All year/split checks passed.") | |
| # ============================================================ | |
| # Weather sanity checks | |
| # ============================================================ | |
| weather_vars = list( | |
| WEATHER_MAP.keys() | |
| ) | |
| for weather_var in weather_vars: | |
| cols = [ | |
| c | |
| for c in out.columns | |
| if c.startswith( | |
| f"{weather_var}_" | |
| ) | |
| ] | |
| assert len(cols) == 52, ( | |
| f"{weather_var}: expected 52 weekly " | |
| f"columns, found {len(cols)}" | |
| ) | |
| print() | |
| print("Weather variables:") | |
| print(weather_vars) | |
| print( | |
| "Weeks per weather variable:", | |
| 52, | |
| ) | |
| print( | |
| "Total weather columns:", | |
| 52 * len(weather_vars), | |
| ) | |
| # ============================================================ | |
| # Soil sanity checks | |
| # ============================================================ | |
| assert len(SOIL_VARS) == 66 | |
| assert all( | |
| col in out.columns | |
| for col in SOIL_VARS | |
| ) | |
| print( | |
| "Number of soil variables:", | |
| len(SOIL_VARS), | |
| ) | |
| # ============================================================ | |
| # Check expected tensor dimensions | |
| # ============================================================ | |
| print() | |
| print("Expected model input dimensions:") | |
| print( | |
| "weather = [52, 6]" | |
| ) | |
| print( | |
| "soil = [66]" | |
| ) | |
| # ============================================================ | |
| # Yield summaries | |
| # ============================================================ | |
| print() | |
| print("===================================") | |
| print("YIELD SUMMARY") | |
| print("===================================") | |
| print("\nTrain:") | |
| print( | |
| train_df[ | |
| "yield" | |
| ].describe() | |
| ) | |
| print("\nValidation:") | |
| print( | |
| val_df[ | |
| "yield" | |
| ].describe() | |
| ) | |
| print("\nTest:") | |
| print( | |
| test_df[ | |
| "yield" | |
| ].describe() | |
| ) | |
| # ============================================================ | |
| # Save HDF5 files | |
| # ============================================================ | |
| OUTPUT_DIR.mkdir( | |
| parents=True, | |
| exist_ok=True, | |
| ) | |
| train_path = OUTPUT_DIR / "train.h5" | |
| val_path = OUTPUT_DIR / "val.h5" | |
| test_path = OUTPUT_DIR / "test.h5" | |
| train_df.to_hdf( | |
| train_path, | |
| key="data", | |
| mode="w", | |
| ) | |
| val_df.to_hdf( | |
| val_path, | |
| key="data", | |
| mode="w", | |
| ) | |
| test_df.to_hdf( | |
| test_path, | |
| key="data", | |
| mode="w", | |
| ) | |
| print() | |
| print("===================================") | |
| print("SAVED") | |
| print("===================================") | |
| print(train_path) | |
| print(val_path) | |
| print(test_path) | |
| # ============================================================ | |
| # Reload files to verify they were written correctly | |
| # ============================================================ | |
| train_check = pd.read_hdf(train_path) | |
| val_check = pd.read_hdf(val_path) | |
| test_check = pd.read_hdf(test_path) | |
| assert len(train_check) == len(train_df) | |
| assert len(val_check) == len(val_df) | |
| assert len(test_check) == len(test_df) | |
| assert set( | |
| train_check["year"].unique() | |
| ) == { | |
| 2013, | |
| 2014, | |
| 2015, | |
| 2016, | |
| } | |
| assert set( | |
| val_check["year"].unique() | |
| ) == { | |
| 2017, | |
| } | |
| assert set( | |
| test_check["year"].unique() | |
| ) == { | |
| 2018, | |
| } | |
| print() | |
| print("HDF5 reload verification passed.") | |
| print() | |
| print("Done.") | |