1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ def prepare_gdd (weather_df , sim_start , sim_end , gdd , crop , sum_fun ):
5
+ """
6
+ function to read in GDD and crop data to return
7
+ mean or median GDD crop growth stages based on all seasons
8
+ in the simulation period
9
+ Arguments:
10
+ weather_df(DataFrame): weather data across entire simulation timespan
11
+
12
+ sim_start (str): simulation start date
13
+
14
+ sim_end (str): simulation end date
15
+
16
+ gdd (numeric vector): daily gdd values across entire simulation time
17
+
18
+ crop (Crop object): Crop object containing crop parameters
19
+
20
+ sum_fun (str): 'mean' or 'median', select method of summarising
21
+ multiple years of GDD growth stages calculations
22
+ Returns:
23
+ crop (Crop object): updated crop object with new GDD growth stages
24
+ calculated based on entire simulation period
25
+ """
26
+ # convert str to datetime
27
+ sim_start_date = pd .to_datetime (sim_start )
28
+ sim_end_date = pd .to_datetime (sim_end )
29
+
30
+ # add gdd as column
31
+ assert len (gdd ) == len (weather_df ), "The length of 'gdd' does not match the number of rows in 'weather_df', check planting date is on or after simulation start date in first year."
32
+ weather_df ['gdd' ]= gdd
33
+
34
+ # Convert mm/dd formatted dates to datetime objects
35
+ def parse_mmdd_to_datetime (mmdd_date , year ):
36
+ mm , dd = map (int , mmdd_date .split ('/' ))
37
+ return pd .to_datetime (f'{ year } -{ mm :02d} -{ dd :02d} ' )
38
+
39
+ # Initialize the season counter
40
+ season_counter = 1
41
+
42
+ current_year = sim_start_date .year
43
+ planting_date = crop .planting_date
44
+
45
+ # Determine the first planting date
46
+ first_planting_date = parse_mmdd_to_datetime (planting_date , current_year )
47
+ if first_planting_date < sim_start_date :
48
+ current_year += 1
49
+ first_planting_date = parse_mmdd_to_datetime (planting_date , current_year )
50
+
51
+ # Loop through each year to assign seasons
52
+ while first_planting_date <= sim_end_date :
53
+ # Determine the end date of the season (a day before the next planting date)
54
+ next_planting_date = parse_mmdd_to_datetime (planting_date , current_year + 1 )
55
+ season_end_date = next_planting_date - pd .Timedelta (days = 1 )
56
+
57
+ # If the season end date is beyond the simulation end date, truncate it
58
+ if season_end_date > sim_end_date :
59
+ season_end_date = sim_end_date
60
+
61
+ # Update the 'season' column for the current season
62
+ weather_df .loc [(weather_df ['Date' ] >= first_planting_date ) & (weather_df ['Date' ] <= season_end_date ), 'season' ] = season_counter
63
+
64
+ # Increment the season counter
65
+ season_counter += 1
66
+
67
+ # Move to the next year
68
+ current_year += 1
69
+ first_planting_date = next_planting_date
70
+
71
+ # List of growth stages
72
+ growth_stages = [
73
+ 'Emergence' , 'Canopy10Pct' , 'MaxRooting' , 'MaxCanopy' , 'CanopyDevEnd' ,
74
+ 'Senescence' , 'Maturity' , 'HIstart' , 'HIend' , 'YieldFormation'
75
+ ]
76
+ if crop .CropType == 3 :
77
+ growth_stages .extend (['FloweringEnd' , 'FloweringDuration' ])
78
+
79
+ # Create dictionary of lists to store yearly GDD values for each growth stage
80
+ # i.e. create as many lists as there are elements in 'growth_stages',
81
+ # named with the values in 'growth_stages' in a dictionary
82
+ gdd_lists = {f'{ stage } ' : [] for stage in growth_stages }
83
+
84
+ # get list of season numbers to iterate across
85
+ seasons = weather_df ['season' ].unique ()
86
+
87
+ # iterate across seasons, calculating GDD to each CD growth stage
88
+ # per season and storing in the gdd_lists lists
89
+ for season in seasons :
90
+ # filter to current season
91
+ season_data = weather_df [weather_df ['season' ]== season ]
92
+
93
+ # get cumulative GDD for current season
94
+ gdd_cum = np .cumsum (season_data ['gdd' ])
95
+
96
+ # Find GDD equivalent for each crop calendar day growth stage
97
+ gdd_lists ['Emergence' ].append (gdd_cum .iloc [int (crop .EmergenceCD )])
98
+ gdd_lists ['Canopy10Pct' ].append (gdd_cum .iloc [int (crop .Canopy10PctCD )])
99
+ gdd_lists ['MaxRooting' ].append (gdd_cum .iloc [int (crop .MaxRootingCD )])
100
+ gdd_lists ['MaxCanopy' ].append (gdd_cum .iloc [int (crop .MaxCanopyCD )])
101
+ gdd_lists ['CanopyDevEnd' ].append (gdd_cum .iloc [int (crop .CanopyDevEndCD )])
102
+ gdd_lists ['Senescence' ].append (gdd_cum .iloc [int (crop .SenescenceCD )])
103
+ gdd_lists ['Maturity' ].append (gdd_cum .iloc [int (crop .MaturityCD )])
104
+ gdd_lists ['HIstart' ].append (gdd_cum .iloc [int (crop .HIstartCD )])
105
+ gdd_lists ['HIend' ].append (gdd_cum .iloc [int (crop .HIendCD )])
106
+ gdd_lists ['YieldFormation' ].append (crop .HIend - crop .HIstart )
107
+
108
+ # Duration of flowering (gdd's) - (fruit/grain crops only)
109
+ if crop .CropType == 3 :
110
+ flowering_end = gdd_cum .iloc [int (crop .FloweringEndCD )]
111
+ # gdd's from sowing to end of flowering
112
+ gdd_lists ['FloweringEnd' ].append (flowering_end )
113
+ # Duration of flowering (gdd's)
114
+ gdd_lists ['FloweringDuration' ].append (flowering_end - crop .HIstart )
115
+
116
+ # calculate mean/median of GDD growth stages using dictionary logic,
117
+ # set the attribute to update the crop object
118
+ if sum_fun == 'mean' :
119
+ for stage , values in gdd_lists .items ():
120
+ setattr (crop , stage , np .mean (values ))
121
+ elif sum_fun == 'median' :
122
+ for stage , values in gdd_lists .items ():
123
+ setattr (crop , stage , np .median (values ))
124
+
125
+ return crop
0 commit comments