Encode hour of day so that 23:00 and 00:00 come out close together.
Hour of day is cyclical — 23:00 is one hour away from 00:00, but a plain integer column puts them 23 apart.
Constraints:
- Return two new columns instead of the raw hour.
- The encoding must generalise to any period, so take the period as an argument.
- Use only numpy and pandas.
import numpy as np
import pandas as pd
def encode_cyclical(df: pd.DataFrame, col: str, period: int) -> pd.DataFrame:
# TODO: add <col>_sin and <col>_cos columns and drop the raw column
...
Complete the implementation.
Map the value onto a circle — take the angle two pi times hour over 24 and emit its sine and cosine as two columns. Because the circle closes, hour 23 and hour 0 land next to each other, which a plain integer column cannot express.
- ✗Leaving the raw integer hour in place, so the model still sees midnight as far from 23:00
- ✗Emitting only sine, which collides two different hours onto the same value
- ✗Hard-coding a period of 24 and reusing the helper for month or weekday
- →Why does a single sine column collide two different hours onto one value?
- →Does a gradient boosted tree gain anything from this encoding?
The value is mapped onto a circle: the angle is 2*pi*value/period, and the features are its sine and cosine. The circle closes, so the values at its two ends end up next to each other.
import numpy as np
import pandas as pd
def encode_cyclical(df: pd.DataFrame, col: str, period: int) -> pd.DataFrame:
out = df.copy()
angle = 2 * np.pi * out[col].astype(float) / period
out[f"{col}_sin"] = np.sin(angle)
out[f"{col}_cos"] = np.cos(angle)
return out.drop(columns=[col])
df = pd.DataFrame({"hour": [23, 0, 12]})
enc = encode_cyclical(df, "hour", 24)
The distance between hour 23 and hour 0 in the (sin, cos) pair equals the distance between 0 and 1, whereas the raw integer put them 23 apart. The cosine is mandatory: sine alone gives the same value for two different hours. Pass a period of 12 for month and 7 for weekday.