Pracuję z dataframe, który wygląda w ten sposób:

df = pd.DataFrame({'ID':['A','A','A','A','B','B','B','B'],'X':[1.2,2.1,3.8,4.5,5.8,6.2,7,8.2],'Y':[10,20,30,40,50,60,70,80],'IsTrue':[1,1,0,0,1,0,0,1],'IdxVar':[1,0,0,0,0,0,0,1]})
df["DistanceToIdx"] = np.sqrt((df.X - df.X[df.groupby('ID')['IdxVar'].transform('idxmax')].reset_index(drop=True))**2 \
                        +(df.Y - df.Y[df.groupby('ID')['IdxVar'].transform('idxmax')].reset_index(drop=True))**2)

Próbuję utworzyć nowy plik df, który ma jeden wiersz na wartość identyfikatora, na podstawie IdxVar. Wystarczająco proste

newdf = df.loc[df.IdxVar==1,:]

Chcę, aby moje nowe kolumny to jakieś X0_1, X0_2, X1_1, X0_2, gdzie Xi_j można zobaczyć jako X wartości for i = IsTrue (0 lub 1) i j wskazuje posortowany indeks DistanceToIdx (X0_1 wskazuje wartość X w danym identyfikatorze, który ma IsTrue = 0 i najniższą DistanceToIdx).

Mogę to zrobić za pośrednictwem groupby():

groupdf = df.groupby('ID')

for name,group in groupdf:
    for i in range(2):

        newdf.loc[newdf.ID==name, 'X0_{}'.format(i+1)] = \
            group.sort_values(by=['IsTrue','DistanceToIdx'],ascending=True)['X'].values[i]

        newdf.loc[newdf.ID==name, 'X1_{}'.format(i+1)] = \
            group.sort_values(by=['IsTrue','DistanceToIdx'],ascending=True)['X'].values[i+2]

Daje to pożądane wyjście, ale jeśli chcę zastosować go do większej liczby zmiennych niż pokazano tutaj, a następnie zapętlić przez 100 000 grup, moja pętla trwa zbyt długo.

Zastanawiałem się, czy można by to przyspieszyć za pomocą tylko funkcji grupowania. Moją początkową myślą było po prostu wymyślenie funkcji przestawnej, ale ponieważ chcę uporządkować nowe kolumny na podstawie istniejącego wiersza, nie jestem zbyt pewien na podstawie dokumentacji, że będzie działać.

3
yankeefan11 20 listopad 2019, 05:03

2 odpowiedzi

Używałbym GroupBy.cumcount aby utworzyć indeksy j i móc obracać tabelę (DataFrame.pivot_table) rozróżnianie w 4 kolumnach. Następnie po prostu dołącz go do ramki, gdzie IdxVar==1, używając DataFrame.join

new_df=df.copy()
#creating columns to pivot_table and set the name of the columns
new_df['id2']=df.groupby(['ID','IsTrue']).IsTrue.cumcount()+1

#Selecting IDxVar1 --->df1
df1=df[df.IdxVar.eq(1)]

#Using pivot_table
#new_df=new_df.sort_values(by=['IsTrue','DistanceToIdx'],ascending=True)
df2=new_df.pivot_table(index='ID',columns=['id2','IsTrue'],values='X')

#join both dataframes
new_df=df1.join(df2,on='ID')

#creating the names of columns
new_df.columns =df.columns.tolist() + [f'X{i}_{j}' for j,i in df2.columns]
print(new_df)

Wyjście

  ID    X   Y  IsTrue  IdxVar  DistanceToIdx  X0_1  X1_1  X0_2  X1_2
0  A  1.2  10       1       1            0.0   3.8   1.2   4.5   2.1
7  B  8.2  80       1       1            0.0   7.0   8.2   6.2   5.8
0
ansev 21 listopad 2019, 00:36
Więc jak to sortuje według zmiennej „DistanceToIdx”?
 – 
yankeefan11
20 listopad 2019, 17:46
Myślę, że tutaj nie trzeba zamawiać. Ponieważ zamówienia w tabeli przestawnej są we właściwej kolejności. Podobnie jak w przypadku używania groupby dane są sortowane, ponieważ sort = True domyślnie w metodzie groupby. Czy masz jakiś błąd w tym kodzie?
 – 
ansev
20 listopad 2019, 19:44
Zaktualizowałem swoje rozwiązanie. Możesz użyć new_df=new_df.sort_values(by=['IsTrue','DistanceToIdx'],ascending=True)
 – 
ansev
21 listopad 2019, 00:36

Jak już utworzyłeś newdf. Wymyślam rozwiązanie, używając nsmallest, aby uzyskać 2 najmniejsze wartości z każdej grupy, unstack i spłaszczyć kolumny z wieloma indeksami. Na koniec wróć z powrotem do newdf

df1 = (df.set_index('X').groupby(['ID', 'IsTrue']).DistanceToIdx.nsmallest(2).
          reset_index(level=-1).drop('DistanceToIdx', 1))
s = df1.groupby(level=[0,1]).cumcount().add(1)
df2 = df1.set_index(s, append=True).unstack([1,2]).sort_index(level=2, axis=1)
df2.columns = df2.columns.map('{0[0]}{0[1]}_{0[2]}'.format)

df_final = newdf.merge(df2.reset_index(), on='ID')


Out[239]:

  ID    X   Y  IsTrue  IdxVar  DistanceToIdx  X0_1  X1_1  X0_2  X1_2
0  A  1.2  10       1       1            0.0   3.8   1.2   4.5   2.1
1  B  8.2  80       1       1            0.0   7.0   8.2   6.2   5.8
1
ansev 20 listopad 2019, 06:49
Wygląda na to, że działa to całkiem nieźle na moim większym zbiorze danych! Kontynuacja: jeśli chcę rozszerzyć w tym zestawie na X i Y, czy jest to tak proste, jak wykonanie początkowego set_indes (['X','Y'])? A może multiindeks będzie trudniejszy?
 – 
yankeefan11
20 listopad 2019, 17:56
Jest prawie tak samo. Oprócz set_indes(['X','Y']), musisz dodać i zmienić niektóre poziomy jako.: ...reset_index(level=[-1,-2])...
 – 
Andy L.
20 listopad 2019, 20:41