The accepted answer by @DSM has a problem highlighted in the comments:
Be careful with dense because if the values are the same, they get assigned the same rank! – Valerio Ficcadenti
This may cause your code to break if you rely on the uniqueness of (group_ID, rank)
.
The following code uses argsort within each group and inverts the map, so it does not suffer from the same problem:
df["rank"] = df.groupby("group_ID")["value"].transform(
lambda x: x.to_frame("value")
.assign(t=np.arange(x.shape[0]))["t"]
.map({
y: x.shape[0] - i
for i, y in enumerate(x.argsort())
}))