Here's a solution you can use with groupby
:
# convert nans to str
df["names"] = df["names"].fillna("NaN")
# assign a subgroup to each set of consecutive rows
df["subgroup"] = df["names"].ne(df["names"].shift()).cumsum()
# take the max length of any subgroup that belongs to "name"
def get_max_consecutive(name):
return df.groupby(["names", "subgroup"]).apply(len)[name].max()
for name in df.names.unique():
print(f"{name}: {get_max_consecutive(name)}")
Output:
Alan: 3
John: 2
NaN: 5
Christy: 2
Explanation:
pandas.Series.ne
takes two series and returns a new series that is True for the elements in each row are not equal and False if they are equal.
We can use df["names"]
and compare it to itself, except shifted by 1 (df["names"].shift()
). This will return True whenever name changes from the previous value.
So this gives us a boolean series where each True
marks a change in name:
df["names"].ne(df["names"].shift())
0 True
1 False
2 True
3 False
4 True
5 False
6 False
7 True
8 False
9 False
10 False
11 False
12 True
13 False
14 True
Name: names, dtype: bool
Then, .cumsum
is just a cumulative sum of this series. In this case, True is equal to 1 and False is 0. This effectively gives us a new number each time the name changes from the previous value. We can assign this to its own column subgroup
so we use groupby with it later.
df.names.ne(df.names.shift()).cumsum()
0 1
1 1
2 2
3 2
4 3
5 3
6 3
7 4
8 4
9 4
10 4
11 4
12 5
13 5
14 6
Name: names, dtype: int64
Lastly, we can use .groupby
to group the dataframe using a multi-index on the "names" and "subgroups" columns. Now we can apply the len
function to get the length of each subgroup.
df.groupby(["names", "subgroup"]).apply(len)
names subgroup
Alan 1 2
3 3
Christy 5 2
John 2 2
6 1
NaN 4 5
dtype: int64
Bonus: You can turn the series returned by .apply
into a dataframe using .reset_index
if you'd like to see the len of each name and subgroup:
df_count = df.groupby(["names", "subgroup"]).apply(len).reset_index(name="len")
df_count
Output:
names subgroup len
0 Alan 1 2
1 Alan 3 3
2 Christy 5 2
3 John 2 2
4 John 6 1
5 NaN 4 5