yield-estimation / training_code /scripts /prepare_cornbelt.py
Sarikaa-Sridhar's picture
Upload 21 files
98024ab verified
Raw
History Blame Contribute Delete
11.7 kB
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.")