사전을 기반으로 데이터 프레임에 새 열 추가


23

데이터 프레임과 사전이 있습니다. 데이터 프레임에 새 열을 추가하고 사전을 기반으로 해당 값을 계산해야합니다.

기계 학습, 일부 테이블을 기반으로 새로운 기능 추가 :

score = {(1, 45, 1, 1) : 4, (0, 1, 2, 1) : 5}
df = pd.DataFrame(data = {
    'gender' :      [1,  1,  0, 1,  1,  0,  0,  0,  1,  0],
    'age' :         [13, 45, 1, 45, 15, 16, 16, 16, 15, 15],
    'cholesterol' : [1,  2,  2, 1, 1, 1, 1, 1, 1, 1],
    'smoke' :       [0,  0,  1, 1, 7, 8, 3, 4, 4, 2]},
     dtype = np.int64)

print(df, '\n')
df['score'] = 0
df.score = score[(df.gender, df.age, df.cholesterol, df.smoke)]
print(df)

다음과 같은 결과가 기대됩니다.

   gender  age  cholesterol  smoke    score
0       1   13            1      0      0 
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0

답변:


13

이후 score(키가 고유 때문에) 사전은 우리가 사용할 수있는 MultiIndex정렬을

df = df.set_index(['gender', 'age', 'cholesterol', 'smoke'])
df['score'] = pd.Series(score)  # Assign values based on the tuple
df = df.fillna(0, downcast='infer').reset_index()  # Back to columns

   gender  age  cholesterol  smoke  score
0       1   13            1      0      0
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0

1
좋은 중 하나입니다 MultiIIndex. 대안 : df['score'] =df.set_index(['gender', 'age', 'cholesterol', 'smoke']).index.map(score).fillna(0).to_numpy().
Quang Hoang

4
@ALollz, 저를 용서하십시오, 나는 당신의 답변을 좋아하지만 이런 답변에 너무 많은 공감대를 볼 때 나는 말해야합니다. 이 답변은 괜찮 영리. 그러나 그것은 좋지 않습니다. 큰 이익을 얻지 못하는 움직이는 부품이 너무 많습니다. 이 과정에서 새로운 via 생성자 인 새로운 via df를 만들었습니다 . 에 할당 할 때 인덱스 정렬의 이점이 있지만 . 마지막으로, 작업을 완료하지만 많은 팬더 객체를 불필요하게 생성 하여이 긴 솔루션을 선호해서는 안됩니다. set_indexSeriesdf['score']fillna(0, downcast='infer')
17:00에

다시 한번, 사과드립니다, 당신은 저의 공감대도 가지고 있습니다. 저는 사람들을 간단한 답변으로 안내하고 싶습니다.
17:00에

@piRSquared 나는 점심 먹으러 갔고, 내가 돌아 왔을 때주의를 기울인 것에 놀랐습니다. 나는 간단한 merge것이 달성 할 수 있는 일을하는 것이 조금 복잡하다는 데 동의한다 . 나는 답변이 빨리 게시 될 것이라고 생각하여 대안을 선택했으며 어떤 이유로 MultiIndices가 마음에 들었습니다. 나는 이것이 아마도 받아 들여질만한 대답이 아니어야한다는 것에 동의합니다.
ALollz

1
오 당신과 함께 있어요 나는 여러 번 같은 대답을했습니다. 난 그냥 (지역 사회 서비스를 제공하기 위해 최선을 다하고 있어요 - : 난 당신이 내 의도를 얻을 신뢰합니다.
piRSquared

7

assign목록 이해와 함께 사용 하여 score사전 에서 튜플 값 (각 행)을 가져 오며 찾을 수없는 경우 기본값은 0입니다.

>>> df.assign(score=[score.get(tuple(row), 0) for row in df.values])
   gender  age  cholesterol  smoke  score
0       1   13            1      0      0
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0

타이밍

다양한 접근 방식을 고려할 때 일부 타이밍을 비교하는 것이 흥미로울 것입니다.

# Initial dataframe 100k rows (10 rows of identical data replicated 10k times).
df = pd.DataFrame(data = {
    'gender' :      [1,  1,  0, 1,  1,  0,  0,  0,  1,  0] * 10000,
    'age' :         [13, 45, 1, 45, 15, 16, 16, 16, 15, 15] * 10000,
    'cholesterol' : [1,  2,  2, 1, 1, 1, 1, 1, 1, 1] * 10000,
    'smoke' :       [0,  0,  1, 1, 7, 8, 3, 4, 4, 2] * 10000},
     dtype = np.int64)

%timeit -n 10 df.assign(score=[score.get(tuple(v), 0) for v in df.values])
# 223 ms ± 9.28 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10 
df.assign(score=[score.get(t, 0) for t in zip(*map(df.get, df))])
# 76.8 ms ± 2.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
df.assign(score=[score.get(v, 0) for v in df.itertuples(index=False)])
# 113 ms ± 2.58 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit -n 10 df.assign(score=df.apply(lambda x: score.get(tuple(x), 0), axis=1))
# 1.84 s ± 77.3 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
(df
 .set_index(['gender', 'age', 'cholesterol', 'smoke'])
 .assign(score=pd.Series(score))
 .fillna(0, downcast='infer')
 .reset_index()
)
# 138 ms ± 11.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
s=pd.Series(score)
s.index.names=['gender','age','cholesterol','smoke']
df.merge(s.to_frame('score').reset_index(),how='left').fillna(0).astype(int)
# 24 ms ± 2.27 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
df.assign(score=pd.Series(zip(df.gender, df.age, df.cholesterol, df.smoke))
                .map(score)
                .fillna(0)
                .astype(int))
# 191 ms ± 7.54 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit -n 10
df.assign(score=df[['gender', 'age', 'cholesterol', 'smoke']]
                .apply(tuple, axis=1)
                .map(score)
                .fillna(0))
# 1.95 s ± 134 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

내가 가장 좋아하는 것. 그러나 score.get사용을 통해 처리 할 때 모든 것이 의도 된 유형을 유지하도록 itertuples또는 zip(*map(df.get, df))... 반복해서 말하면 이것이 선호되는 접근법입니다.
piRSquared

1
df.assign(score=[score.get(t, 0) for t in zip(*map(df.get, df))])
piRSquared

1
마지막으로, 내가 쓰는 것의 대부분은 해시 1.0와 동일 하기 때문에 블로 스터 입니다. 1따라서 튜플 조회는 상관없이 동일한 대답을 가져야합니다. 사과이에 대한 많은 의견 @Alexander하지만 난 그냥 사람들이 더 많은 찬성 투표를 원하기 때문에 ... 그들이해야한다 (- :
piRSquared

1
당신이 타이밍을 유지하는 한 내 제안을 살펴보십시오. .values비싼 경우 가 있습니다
초에 piRSquared

1
@AndyL. 당신도 할 열 및 순서를 제어 할 수 있습니다 zip(*map(df.get, ['col2', 'col1', 'col5']))또는 수정의 튜플을 얻을 df:zip(*map(df.eq(1).get, df))
piRSquared

4

score는 사전이므로 map을 사용할 수 있습니다 .

df['score'] = df[['gender', 'age', 'cholesterol', 'smoke']].apply(tuple, axis=1).map(score).fillna(0)
print(df)

산출

   gender  age  cholesterol  smoke  score
0       1   13            1      0    0.0
1       1   45            2      0    0.0
2       0    1            2      1    5.0
3       1   45            1      1    4.0
4       1   15            1      7    0.0
5       0   16            1      8    0.0
6       0   16            1      3    0.0
7       0   16            1      4    0.0
8       1   15            1      4    0.0
9       0   15            1      2    0.0

대안으로 목록 이해를 사용할 수 있습니다.

df['score'] = [score.get(t, 0) for t in zip(df.gender, df.age, df.cholesterol, df.smoke)]
print(df)

내 질문을 확장하고 싶습니다. 실제로 열 값 범위에 열 기준을 추가해야합니다. 예를 들어, 40 <age <50이면 score = 4 etc ... 이제 사전은 정확한 값으로 매핑됩니다. 똑같은 사실과 다른 열쇠들도 ....
Mikola

1
당신이 정말로 원하는 것의 예를 추가하십시오
Dani Mesejo

간단한 예 : # 여기서 40과 50, 10과 20은 점수 = 4 (또는 5) 점수 = {(1, 40, 50, 1, 1) : 4, (0, 10, 20)을 사용해야하는 연령대입니다. , 1, 3) : 5}
Mikola

@Mikola 따라서 성별 = 1 및 40 <연령 <50 등
이면

1
@Mikola이 시점에서 다른 질문을하면 더 좋다고 생각하지만 모든 신체에 알려야합니다.
Dani Mesejo

4

목록 이해 및지도 :

df['score'] = (pd.Series(zip(df.gender, df.age, df.cholesterol, df.smoke))
               .map(score)
               .fillna(0)
               .astype(int)
              )

산출:

   gender  age  cholesterol  smoke  score
0       1   13            1      0      0
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0
9       0   15            1      2    0.0

4

reindex

df['socre']=pd.Series(score).reindex(pd.MultiIndex.from_frame(df),fill_value=0).values
df
Out[173]: 
   gender  age  cholesterol  smoke  socre
0       1   13            1      0      0
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0

또는 merge

s=pd.Series(score)
s.index.names=['gender','age','cholesterol','smoke']
df=df.merge(s.to_frame('score').reset_index(),how='left').fillna(0)
Out[166]: 
   gender  age  cholesterol  smoke  score
0       1   13            1      0    0.0
1       1   45            2      0    0.0
2       0    1            2      1    5.0
3       1   45            1      1    4.0
4       1   15            1      7    0.0
5       0   16            1      8    0.0
6       0   16            1      3    0.0
7       0   16            1      4    0.0
8       1   15            1      4    0.0
9       0   15            1      2    0.0

2

다른 방법으로 사용할 수 있습니다 .loc[]:

m=df.set_index(df.columns.tolist())
m.loc[list(score.keys())].assign(
           score=score.values()).reindex(m.index,fill_value=0).reset_index()

   gender  age  cholesterol  smoke  score
0       1   13            1      0      0
1       1   45            2      0      0
2       0    1            2      1      5
3       1   45            1      1      4
4       1   15            1      7      0
5       0   16            1      8      0
6       0   16            1      3      0
7       0   16            1      4      0
8       1   15            1      4      0
9       0   15            1      2      0

2

간단한 한 줄 솔루션, 사용 gettuple행 단위

df['score'] = df.apply(lambda x: score.get(tuple(x), 0), axis=1)

위의 솔루션은 원하는 열 이외의 열이 순서대로 없다고 가정합니다. 그렇지 않은 경우 열만 사용하십시오.

cols = ['gender','age','cholesterol','smoke']
df['score'] = df[cols].apply(lambda x: score.get(tuple(x), 0), axis=1)

사용 score.get은 좋습니다. 그러나 내 의견으로는 이해력을 선호해야합니다. @Alexander의 타이밍을 참조하십시오 .
piRSquared

좋아 @piSquared. 그것을 명심하십시오.
Vishnudev
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.