Chris Parmer — home

Growing-degree-day (GDD) accumulation for corn, 2021-2023

Agronomy

Example from the compendium of canonical charts

Agronomy — Growing-degree-day (GDD) accumulation for corn, 2021-2023

Python Code

"""Agronomy — Growing-degree-day (GDD) accumulation for corn, 2021-2023."""


import numpy as np
import pandas as pd
import plotly.graph_objects as go


# Champaign-Urbana, IL (corn country)
LAT, LON = 40.11, -88.21
YEARS = [2021, 2022, 2023]
COLORS = [VIOLET, GREEN, TEAL]

BASE_URL = (
    "https://archive-api.open-meteo.com/v1/archive"
    "?latitude={lat}&longitude={lon}"
    "&start_date={start}&end_date={end}"
    "&daily=temperature_2m_max,temperature_2m_min"
    "&temperature_unit=fahrenheit"
    "&timezone=America%2FChicago"
)

def gdd_corn(tmax, tmin):
    """GDD for corn with 50°F base and 86°F ceiling."""
    tmax_capped = min(tmax, 86)
    tmin_floored = max(tmin, 50)
    return max(0.0, (tmax_capped + tmin_floored) / 2.0 - 50.0)


def generate():
    fig = go.Figure()

    for year, color in zip(YEARS, COLORS):
        start = f"{year}-04-01"
        end = f"{year}-10-31"
        url = BASE_URL.format(lat=LAT, lon=LON, start=start, end=end)

        try:
            data = fetch_json(url)
            daily = data["daily"]
            tmax_arr = daily["temperature_2m_max"]
            tmin_arr = daily["temperature_2m_min"]
            print(f"{year}: {len(tmax_arr)} days")

            gdds = []
            for tmax, tmin in zip(tmax_arr, tmin_arr):
                if tmax is None or tmin is None:
                    gdds.append(np.nan)
                else:
                    gdds.append(gdd_corn(float(tmax), float(tmin)))

            gdds = np.array(gdds, dtype=float)
            cum_gdd = np.nancumsum(gdds)
            day_of_season = np.arange(len(cum_gdd))

        except Exception as e:
            print(f"API failed for {year} ({e}), using synthetic")
            day_of_season = np.arange(213)
            daily_gdd = np.maximum(0, 18 * np.sin(np.pi * day_of_season / 213) ** 1.5)
            scale = {2021: 1.02, 2022: 0.96, 2023: 1.00}[year]
            daily_gdd *= scale
            cum_gdd = np.cumsum(daily_gdd)

        fig.add_trace(go.Scatter(
            x=day_of_season,
            y=cum_gdd,
            mode="lines",
            name=str(year),
            line=dict(color=color, width=2.5),
        ))

    fig.add_hline(y=2700, line_dash="dash", line_color=MUTED, line_width=1.5,
                  annotation_text="2700 GDU (corn maturity)",
                  annotation_position="bottom right",
                  annotation_font=dict(size=11, color=MUTED))

    fig.update_layout(
        xaxis=dict(title="Day of Season (from April 1)", range=[0, 213]),
        yaxis=dict(title="Cumulative GDD (°F base 50)", range=[0, 3200]),
        legend=dict(x=0.01, y=0.99),
    )

    return fig


if __name__ == "__main__":
    generate()

Made with Plotly