Or a better approach, without groupby. Identify the null rows, select the X for which there is at least one non-null values and select the others. You can do this only with boolean masks and an OR (|):
# which values are not-null?
m1 = df['y'].notna()
# which groups only contain null values?
m2 = ~df['x'].isin(df.loc[m1, 'x'])
# keep rows that match either condition above
out = df[m1 | m2]
As a one-liner:
out = df[(m:=df['y'].notna()) | ~df['x'].isin(df.loc[m, 'x'])]
Output:
x y
0 A NaN
2 B 1.0
3 C 2.0
4 C 3.0
5 C 4.0
6 D NaN
7 D NaN
Intermediates:
x y m1 m2 m1 | m2
0 A NaN False True True
1 B NaN False False False
2 B 1.0 True False True
3 C 2.0 True False True
4 C 3.0 True False True
5 C 4.0 True False True
6 D NaN False True True
7 D NaN False True True