lactationcurve.characteristics.method_test_interval
ICAR Test Interval Method for accumulated lactation yield.
Purpose
This module implements the ICAR Test Interval Method (Procedure 2, Section 2) to compute cumulative milk yield from test-day records.
Method Summary
Calculate total milk yield over a lactation by summing three parts:
- First test-day milk yield multiplied by the number of days between calving and the first test day.
- Trapezoidal integration between consecutive test days (the average of two consecutive yields multiplied by the interval length).
- Last test-day milk yield multiplied by the number of days from the last test day to the end of the calculation window.
Formula:
MY = I0M1 + I1(M1 + M2)/2 + I2*(M2 + M3)/2 + ... + I(n-1)(M(n-1) + Mn)/2 + InMn
Where:
MY: total milk yield over the lactation window.M1, M2, ..., Mn: milk yield measured in the 24 hours of each test day.I1, I2, ..., I(n-1): interval lengths (days) between consecutive test days.I0: days from lactation start (calving) to first test day.In: days from last test day to the end of the calculation window (e.g., DIM 305).
Key Entry Points
test_interval_method: Computes cumulative lactation milk yield perTestIdusing start, interval, and end segments.
Column Flexibility
The function accepts several case-insensitive column name aliases and can
create a default TestId if one is missing. Recognized aliases:
- Days in Milk:
["daysinmilk", "dim", "testday"] - Milk Yield:
["milkingyield", "testdaymilkyield", "milkyield", "yield"] - Test Id:
["animalid", "testid", "id"]
It is also possible to provide your own column names so the function can be applied to dataframes with different column naming conventions.
Returns a DataFrame with columns: ["TestId", "LactationMilkYield"].
Notes
- Units: DIM (days in milk) is measured in days, and milk yield can be in kg or lb. The output stays in the same unit as the input.
- Records with
DIM > max_dimare excluded before computation. - This method's main strength is its ease of use and simplicity.
- Its main disadvantage is that it does not account for the shape of the lactation curve, which can lead to underestimation of total yield, especially for lactations with few test days or irregular patterns. Outliers in test-day records can also have a large influence on the final result.
- When a full lactation is known (i.e., test days up to DIM 305), this method will give a higher cumulative milk yield then the sum off all test days, because of the way the start and end contributions are calculated.
- This method gives the same result as the Fleischmann method, which is formulated differently but mathematically equivalent.
Reference
Sargent, F. D., V. H. Lyton, and 0. G. Wall, J r . 1968. Test interval method of calculating Dairy Herd Improvement Association records. J. Dairy Sci. 51:170.
Author: Meike van Leerdam Date: 07-31-2025 Last update: 22-May-2026
1""" 2ICAR Test Interval Method for accumulated lactation yield. 3 4Purpose 5------- 6This module implements the ICAR Test Interval Method (Procedure 2, 7Section 2) to compute cumulative milk yield from test-day records. 8 9Method Summary 10-------------- 11Calculate total milk yield over a lactation by summing three parts: 12 - First test-day milk yield multiplied by the number of days between 13 calving and the first test day. 14 - Trapezoidal integration between consecutive test days (the average of 15 two consecutive yields multiplied by the interval length). 16 - Last test-day milk yield multiplied by the number of days from the last 17 test day to the end of the calculation window. 18 19Formula: 20 21 MY = I0*M1 22 + I1*(M1 + M2)/2 23 + I2*(M2 + M3)/2 24 + ... 25 + I(n-1)*(M(n-1) + Mn)/2 26 + In*Mn 27 28Where: 29- ``MY``: total milk yield over the lactation window. 30- ``M1, M2, ..., Mn``: milk yield measured in the 24 hours of each test day. 31- ``I1, I2, ..., I(n-1)``: interval lengths (days) between consecutive test days. 32- ``I0``: days from lactation start (calving) to first test day. 33- ``In``: days from last test day to the end of the calculation window (e.g., DIM 305). 34 35 36Key Entry Points 37---------------- 38- ``test_interval_method``: Computes cumulative lactation milk yield per 39 ``TestId`` using start, interval, and end segments. 40 41 42Column Flexibility 43------------------ 44The function accepts several case-insensitive column name aliases and can 45create a default ``TestId`` if one is missing. Recognized aliases: 46 47- Days in Milk: `["daysinmilk", "dim", "testday"]` 48- Milk Yield: `["milkingyield", "testdaymilkyield", "milkyield", "yield"]` 49- Test Id: `["animalid", "testid", "id"]` 50 51It is also possible to provide your own column names so the function 52can be applied to dataframes with different column naming conventions. 53 54Returns a DataFrame with columns: ``["TestId", "LactationMilkYield"]``. 55 56Notes 57----- 58- Units: DIM (days in milk) is measured in days, 59 and milk yield can be in kg or lb. The 60 output stays in the same unit as the input. 61- Records with ``DIM > max_dim`` are excluded before computation. 62- This method's main strength is its ease of use and simplicity. 63- Its main disadvantage is that it does not account for the shape of the 64 lactation curve, which can lead to underestimation of total yield, 65 especially for lactations with few test days or irregular patterns. 66 Outliers in test-day records can also have a large influence on the final 67 result. 68- When a full lactation is known (i.e., test days up to DIM 305), 69this method will give a higher cumulative milk yield then the sum off all test days, 70because of the way the start and end contributions are calculated. 71- This method gives the same result as the Fleischmann method, 72which is formulated differently but mathematically equivalent. 73 74Reference 75--------- 76Sargent, F. D., V. H. Lyton, and 0. G. Wall, J r . 1968. Test interval method of 77calculating Dairy Herd Improvement Association records. J. Dairy Sci. 51:170. 78 79Author: Meike van Leerdam 80Date: 07-31-2025 81Last update: 22-May-2026 82""" 83 84import pandas as pd 85 86from lactationcurve.preprocessing import standardize_lactation_columns 87 88 89def test_interval_method( 90 df: pd.DataFrame, 91 days_in_milk_col: str | None = None, 92 milking_yield_col: str | None = None, 93 test_id_col: str | None = None, 94 default_test_id: int = 0, 95 max_dim: int = 305, 96) -> pd.DataFrame: 97 """Compute total lactation milk yield using the ICAR Test Interval Method. 98 99 The method applies: 100 - First test day milk yield from calving to the first test day, 101 - Trapezoidal integration between consecutive test days, 102 - Last test day milk yield from the last test day to DIM = max_dim (default = 305). 103 104 Args: 105 df (pd.DataFrame): Input DataFrame with at least DaysInMilk and 106 MilkingYield columns, plus an optional TestId column. Column names 107 can be provided explicitly or matched via known aliases. 108 days_in_milk_col (str | None): Optional column name override for 109 DaysInMilk. 110 milking_yield_col (str | None): Optional column name override for 111 MilkingYield. 112 test_id_col (str | None): Optional column name override for TestId. 113 default_test_id (int): Value used to create a default TestId column if 114 one is missing. 115 max_dim (int): Lactation length used to calculate cumulative 116 production. The default is 305 days. 117 Records with DIM > max_dim are excluded. 118 119 Returns: 120 pd.DataFrame: Two-column DataFrame with 121 - "TestId": identifier per lactation, 122 - "LactationMilkYield": computed total milk yield over the 123 specified window. 124 125 Raises: 126 ValueError: If required columns (DaysInMilk or MilkingYield) cannot be found. 127 128 Notes: 129 - Records with DIM > max_dim are dropped before computation. 130 - At least two data points per TestId are required for trapezoidal integration; 131 otherwise the lactation is skipped. 132 """ 133 134 # Standardize columns and filter DIM <= max_dim 135 df = standardize_lactation_columns( 136 df, 137 days_in_milk_col=days_in_milk_col, 138 milking_yield_col=milking_yield_col, 139 test_id_col=test_id_col, 140 default_test_id=default_test_id, 141 max_dim=max_dim, 142 ) 143 144 result = [] 145 146 # Iterate over each lactation 147 for lactation in df["TestId"].unique(): 148 lactation_df = pd.DataFrame(df[df["TestId"] == lactation]) 149 150 # Sort by DaysInMilk ascending 151 lactation_df.sort_values(by="DaysInMilk", ascending=True, inplace=True) 152 153 if len(lactation_df) < 2: 154 print(f"Skipping TestId {lactation}: not enough data points for interpolation.") 155 continue 156 157 # Start and end points 158 start = lactation_df.iloc[0] 159 end = lactation_df.iloc[-1] 160 161 # Start contribution 162 MY0 = start["DaysInMilk"] * start["MilkingYield"] 163 164 # End contribution 165 MYend = (max_dim + 1 - end["DaysInMilk"]) * end["MilkingYield"] 166 167 # Intermediate trapezoidal contributions 168 lactation_df["width"] = lactation_df["DaysInMilk"].diff().shift(-1) 169 lactation_df["avg_yield"] = ( 170 lactation_df["MilkingYield"] + lactation_df["MilkingYield"].shift(-1) 171 ) / 2 172 lactation_df["trapezoid_area"] = lactation_df["width"] * lactation_df["avg_yield"] 173 174 total_intermediate = lactation_df["trapezoid_area"].sum() 175 176 total_yield = MY0 + total_intermediate + MYend 177 result.append((lactation, total_yield)) 178 179 return pd.DataFrame(result, columns=pd.Index(["TestId", "LactationMilkYield"])) 180 181 182# to prevent pytest from trying to collect this function as a test 183test_interval_method.__test__ = False
90def test_interval_method( 91 df: pd.DataFrame, 92 days_in_milk_col: str | None = None, 93 milking_yield_col: str | None = None, 94 test_id_col: str | None = None, 95 default_test_id: int = 0, 96 max_dim: int = 305, 97) -> pd.DataFrame: 98 """Compute total lactation milk yield using the ICAR Test Interval Method. 99 100 The method applies: 101 - First test day milk yield from calving to the first test day, 102 - Trapezoidal integration between consecutive test days, 103 - Last test day milk yield from the last test day to DIM = max_dim (default = 305). 104 105 Args: 106 df (pd.DataFrame): Input DataFrame with at least DaysInMilk and 107 MilkingYield columns, plus an optional TestId column. Column names 108 can be provided explicitly or matched via known aliases. 109 days_in_milk_col (str | None): Optional column name override for 110 DaysInMilk. 111 milking_yield_col (str | None): Optional column name override for 112 MilkingYield. 113 test_id_col (str | None): Optional column name override for TestId. 114 default_test_id (int): Value used to create a default TestId column if 115 one is missing. 116 max_dim (int): Lactation length used to calculate cumulative 117 production. The default is 305 days. 118 Records with DIM > max_dim are excluded. 119 120 Returns: 121 pd.DataFrame: Two-column DataFrame with 122 - "TestId": identifier per lactation, 123 - "LactationMilkYield": computed total milk yield over the 124 specified window. 125 126 Raises: 127 ValueError: If required columns (DaysInMilk or MilkingYield) cannot be found. 128 129 Notes: 130 - Records with DIM > max_dim are dropped before computation. 131 - At least two data points per TestId are required for trapezoidal integration; 132 otherwise the lactation is skipped. 133 """ 134 135 # Standardize columns and filter DIM <= max_dim 136 df = standardize_lactation_columns( 137 df, 138 days_in_milk_col=days_in_milk_col, 139 milking_yield_col=milking_yield_col, 140 test_id_col=test_id_col, 141 default_test_id=default_test_id, 142 max_dim=max_dim, 143 ) 144 145 result = [] 146 147 # Iterate over each lactation 148 for lactation in df["TestId"].unique(): 149 lactation_df = pd.DataFrame(df[df["TestId"] == lactation]) 150 151 # Sort by DaysInMilk ascending 152 lactation_df.sort_values(by="DaysInMilk", ascending=True, inplace=True) 153 154 if len(lactation_df) < 2: 155 print(f"Skipping TestId {lactation}: not enough data points for interpolation.") 156 continue 157 158 # Start and end points 159 start = lactation_df.iloc[0] 160 end = lactation_df.iloc[-1] 161 162 # Start contribution 163 MY0 = start["DaysInMilk"] * start["MilkingYield"] 164 165 # End contribution 166 MYend = (max_dim + 1 - end["DaysInMilk"]) * end["MilkingYield"] 167 168 # Intermediate trapezoidal contributions 169 lactation_df["width"] = lactation_df["DaysInMilk"].diff().shift(-1) 170 lactation_df["avg_yield"] = ( 171 lactation_df["MilkingYield"] + lactation_df["MilkingYield"].shift(-1) 172 ) / 2 173 lactation_df["trapezoid_area"] = lactation_df["width"] * lactation_df["avg_yield"] 174 175 total_intermediate = lactation_df["trapezoid_area"].sum() 176 177 total_yield = MY0 + total_intermediate + MYend 178 result.append((lactation, total_yield)) 179 180 return pd.DataFrame(result, columns=pd.Index(["TestId", "LactationMilkYield"]))
Compute total lactation milk yield using the ICAR Test Interval Method.
The method applies:
- First test day milk yield from calving to the first test day,
- Trapezoidal integration between consecutive test days,
- Last test day milk yield from the last test day to DIM = max_dim (default = 305).
Arguments:
- df (pd.DataFrame): Input DataFrame with at least DaysInMilk and MilkingYield columns, plus an optional TestId column. Column names can be provided explicitly or matched via known aliases.
- days_in_milk_col (str | None): Optional column name override for DaysInMilk.
- milking_yield_col (str | None): Optional column name override for MilkingYield.
- test_id_col (str | None): Optional column name override for TestId.
- default_test_id (int): Value used to create a default TestId column if one is missing.
- max_dim (int): Lactation length used to calculate cumulative production. The default is 305 days. Records with DIM > max_dim are excluded.
Returns:
pd.DataFrame: Two-column DataFrame with - "TestId": identifier per lactation, - "LactationMilkYield": computed total milk yield over the specified window.
Raises:
- ValueError: If required columns (DaysInMilk or MilkingYield) cannot be found.
Notes:
- Records with DIM > max_dim are dropped before computation.
- At least two data points per TestId are required for trapezoidal integration; otherwise the lactation is skipped.