Question: I have a dataset of 100s of people who have been followed up over varying amounts of time (up to 8 observations per person) and have completed a bunch of tests. The time values are always in an integer sequence for each ID. The goal of my project is to examine these changes in individuals, while sampling each person's data across 3 equally spaced intervals from all their available data. Here is a snapshot of the dataset, with just two tests for brevity.  dict = { "ID": [1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4], "Time": [1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, 1, 2], "AlBa": [5, 2, 1, 8, 7, 6, 5, 9, 7, 6, 4, 2, 3, 1], "Tiri": [4, 3, 2, 10, 9, 5, 4, 5, 4, 3, 2, 1, 4, 4] } df_test = pd.DataFrame(dict) print(df_test) ID Time AlBa Tiri 0 1 1 5 4 1 1 2 2 3 2 1 3 1 2 3 2 1 8 10 4 2 2 7 9 5 2 3 6 5 6 2 4 5 4 7 3 1 9 5 8 3 2 7 4 9 3 3 6 3 10 3 4 4 2 11 3 5 2 1 12 4 1 3 4 13 4 2 1 4  I want to sample each person's data across 3 equally spaced intervals from all their available data, leading to three data points per ID. Accordingly, ID1 gets included because they have exactly three observations. ID 4 gets excluded because they have only two observations. For individuals with more than three, but an odd number of observations (e.g., ID3), I want to keep scores from their first (time 1), last (here, time 5), and middle observation (here, time 3). For individuals with more than three, but an even number of observations (e.g., ID2), I want to keep their first (time 1), last (here, time 4), and find the average of the middle two observations (here, average of times 2 and 3). The final dataset should look like this:  ID Time AlBa Tiri 0 1 1.0 5.0 4 1 1 2.0 2.0 3 2 1 3.0 1.0 2 3 2 1.0 8.0 10 4 2 2.5 6.5 7 5 2 4.0 5.0 4 6 3 1.0 9.0 5 7 3 3.0 6.0 3 8 3 5.0 2.0 1  What is the best way to code this in Pandas? I have data with anywhere between 1 and 8 observations per person.

Answer: Using aggregation per group (groupby.agg) with first/median/last, then filtering the IDs with groupby.size, and reshaping with stack: g = df_test.groupby('ID') s = g.size() out = (g.agg(['first', 'median', 'last']) .loc[lambda d: s[s>2].index] # remove groups with < 2 values .stack().reset_index() #.drop(columns=['level_1']) )  Note that median take the mid-point of the sorted values, if you want the mid-point by position, use a custom function: def mid_point(s): return s.iloc[(len(s)-1)//2:len(s)//2+1].mean() g = df_test.groupby('ID') s = g.size() out = (g.agg(['first', mid_point, 'last']) .loc[lambda d: s[s>2].index] .stack().reset_index() )  Output:  ID level_1 Time AlBa Tiri 0 1 first 1.0 5.0 4.0 1 1 median 2.0 2.0 3.0 2 1 last 3.0 1.0 2.0 3 2 first 1.0 8.0 10.0 4 2 median 2.5 6.5 7.0 5 2 last 4.0 5.0 4.0 6 3 first 1.0 9.0 5.0 7 3 median 3.0 6.0 3.0 8 3 last 5.0 2.0 1.0  As noted by @Lahcen in comments, you can also pre-filter before the groupby.agg with: out = (df_test[df_test.groupby('ID')['ID'].transform('count').gt(2)] .groupby('ID').agg(['first', 'median', 'last']) .stack().reset_index().drop(columns=['level_1']) )  The difference is that in one case you reuse the groupby object (which is faster), but filter after the computation (which is slower). In this alternative you have to compute the groupby twice but avoid aggregating unnecessarily. The best approach might depend on the number of groups and the proportion of groups with less than 3 values. To be tested on the real data.