하나의 열 팬더에서 NxN 행렬 만들기


11

각 행에 목록 값이있는 데이터 프레임이 있습니다.

id     list_of_value
0      ['a','b','c']
1      ['d','b','c']
2      ['a','b','c']
3      ['a','b','c']

한 행과 다른 모든 행에 대해 점수를 계산해야합니다.

예를 들어 :

Step 1: Take value of id 0: ['a','b','c'],
Step 2: find the intersection between id 0 and id 1 , 
        resultant = ['b','c']
Step 3: Score Calculation => resultant.size / id.size

모든 ID에 대해 유사하게 id 0과 id 1,2,3 사이에서 2,3 단계를 반복하십시오.

N x N 데이터 프레임을 생성하고; 이 같은 :

-  0  1    2  3
0  1  0.6  1  1
1  1  1    1  1 
2  1  1    1  1
3  1  1    1  1

현재 내 코드에는 하나의 for 루프가 있습니다.

def scoreCalc(x,queryTData):
    #mathematical calculation
    commonTData = np.intersect1d(np.array(x),queryTData)
    return commonTData.size/queryTData.size

ids = list(df['feed_id'])
dfSim = pd.DataFrame()

for indexQFID in range(len(ids)):
    queryTData = np.array(df.loc[df['id'] == ids[indexQFID]]['list_of_value'].values.tolist())

    dfSim[segmentDfFeedIds[indexQFID]] = segmentDf['list_of_value'].apply(scoreCalc,args=(queryTData,))

더 좋은 방법이 있습니까? for 루프 반복을 수행하는 대신 하나의 apply 함수를 작성할 수 있습니까? 더 빨리 만들 수 있습니까?



1
6이 아니고, 0.6, 결과 크기가 2, id.size = 3
Sriram Arvind Lakshmanakumar

데이터가 얼마나 걸립니까? 그리고 얼마나 많은 값이 발생 list_of_value합니까?
Quang Hoang

각 list_of_value에서 최대 20 개의 값
Sriram Arvind Lakshmanakumar

각각에 없습니다 list_of_value. 나는 모든 행에서 전체를 의미합니다.
Quang Hoang

답변:


7

데이터가 너무 크지 않은 경우 get_dummies값을 인코딩하고 행렬 곱셈을 수행하는 데 사용할 수 있습니다 .

s = pd.get_dummies(df.list_of_value.explode()).sum(level=0)
s.dot(s.T).div(s.sum(1))

산출:

          0         1         2         3
0  1.000000  0.666667  1.000000  1.000000
1  0.666667  1.000000  0.666667  0.666667
2  1.000000  0.666667  1.000000  1.000000
3  1.000000  0.666667  1.000000  1.000000

업데이트 : 다음은 코드에 대한 간단한 설명입니다. 주요 아이디어는 주어진 목록을 하나의 핫 인코딩으로 바꾸는 것입니다.

   a  b  c  d
0  1  1  1  0
1  0  1  1  1
2  1  1  1  0
3  1  1  1  0

우리가이되면, 두 행의 교차점의 크기는 말 01문자가에 의해 표현되는 경우에만, 두 행에 속하기 때문에, 그들의 내적입니다 1모두있다.

이를 염두에두고 처음 사용

df.list_of_value.explode()

각 셀을 시리즈로 변환하고 해당 시리즈를 모두 연결합니다. 산출:

0    a
0    b
0    c
1    d
1    b
1    c
2    a
2    b
2    c
3    a
3    b
3    c
Name: list_of_value, dtype: object

이제이 pd.get_dummies시리즈를 사용 하여 하나의 핫 인코딩 된 데이터 프레임으로 변환합니다.

   a  b  c  d
0  1  0  0  0
0  0  1  0  0
0  0  0  1  0
1  0  0  0  1
1  0  1  0  0
1  0  0  1  0
2  1  0  0  0
2  0  1  0  0
2  0  0  1  0
3  1  0  0  0
3  0  1  0  0
3  0  0  1  0

보시다시피 각 값에는 고유 한 행이 있습니다. 동일한 원래 행에 속하는 것을 하나의 행으로 결합하려고하므로 원래 색인으로 합칠 수 있습니다. 그러므로

s = pd.get_dummies(df.list_of_value.explode()).sum(level=0)

원하는 이진 인코딩 데이터 프레임을 제공합니다. 다음 줄

s.dot(s.T).div(s.sum(1))

논리와 같습니다. s.dot(s.T)도트 제품을 행 .div(s.sum(1))으로 계산 한 다음 카운트를 행으로 나눕니다.


12k 행 데이터 프레임
Sriram Arvind Lakshmanakumar

12k 행의 @SriramArvindLakshmanakumar를 사용하면 12k x 12k데이터 프레임 이 생깁니다 . 약 수백 개의 고유 한 값이 있으면 괜찮습니다.
Quang Hoang

코드도 설명 할 수 있습니까?
Sriram Arvind Lakshmanakumar

물론이지만 작동합니까?
Quang Hoang

1
@SriramArvindLakshmanakumar 내 솔루션을 수락 해 주셔서 감사합니다. 설명 및 사고 논리에 대해서는 업데이트를 참조하십시오.
Quang Hoang

3

이 시도

range_of_ids = range(len(ids))

def score_calculation(s_id1,s_id2):
    s1 = set(list(df.loc[df['id'] == ids[s_id1]]['list_of_value'])[0])
    s2 = set(list(df.loc[df['id'] == ids[s_id2]]['list_of_value'])[0])
    # Resultant calculation s1&s2
    return round(len(s1&s2)/len(s1) , 2)


dic = {indexQFID:  [score_calculation(indexQFID,ind) for ind in range_of_ids] for indexQFID in range_of_ids}
dfSim = pd.DataFrame(dic)
print(dfSim)

산출

     0        1      2       3
0   1.00    0.67    1.00    1.00
1   0.67    1.00    0.67    0.67
2   1.00    0.67    1.00    1.00
3   1.00    0.67    1.00    1.00

다음과 같이 할 수도 있습니다

dic = {indexQFID:  [round(len(set(s1)&set(s2))/len(s1) , 2) for s2 in df['list_of_value']] for indexQFID,s1 in zip(df['id'],df['list_of_value']) }
dfSim = pd.DataFrame(dic)
print(dfSim)

2

set 목록에 중첩 된 목록 이해를 사용하십시오 s_list. 목록 이해 내에서 intersection조작을 사용 하여 겹치는 부분을 확인하고 각 결과의 길이를 얻으십시오. 마지막으로 데이터 프레임을 구성하고 각 목록의 길이로 나누십시오.df.list_of_value

s_list =  df.list_of_value.map(set)
overlap = [[len(s1 & s) for s1 in s_list] for s in s_list]

df_final = pd.DataFrame(overlap) / df.list_of_value.str.len().to_numpy()[:,None]

Out[76]:
          0         1         2         3
0  1.000000  0.666667  1.000000  1.000000
1  0.666667  1.000000  0.666667  0.666667
2  1.000000  0.666667  1.000000  1.000000
3  1.000000  0.666667  1.000000  1.000000

각 목록에 중복 값이있는 경우 collections.Counter대신 대신 사용해야 합니다 set. 샘플 데이터 id = 0을 ['a','a','c']id = 1로 변경했습니다.['d','b','a']

sample df:
id     list_of_value
0      ['a','a','c'] #changed
1      ['d','b','a'] #changed
2      ['a','b','c']
3      ['a','b','c']

from collections import Counter

c_list =  df.list_of_value.map(Counter)
c_overlap = [[sum((c1 & c).values()) for c1 in c_list] for c in c_list]

df_final = pd.DataFrame(c_overlap) / df.list_of_value.str.len().to_numpy()[:,None]


 Out[208]:
          0         1         2         3
0  1.000000  0.333333  0.666667  0.666667
1  0.333333  1.000000  0.666667  0.666667
2  0.666667  0.666667  1.000000  1.000000
3  0.666667  0.666667  1.000000  1.000000

2

업데이트

제안 된 후보 솔루션이 많이 있으므로 타이밍 분석을 수행하는 것이 좋습니다. OP의 요청에 따라 12k 개의 행으로 임의의 데이터를 생성하여 세트 당 3 개의 요소를 유지하면서 세트를 채우는 데 사용할 수있는 알파벳의 크기를 확장했습니다. 실제 데이터와 일치하도록 조정할 수 있습니다.

테스트하거나 업데이트하려는 솔루션이 있으면 알려주십시오.

설정

import pandas as pd
import random

ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

def random_letters(n, n_letters=52):
    return random.sample(ALPHABET[:n_letters], n)

# Create 12k rows to test scaling.
df = pd.DataFrame([{'id': i, 'list_of_value': random_letters(3)} for i in range(12000)])

현재 우승자

def method_quang(df): 
    s = pd.get_dummies(df.list_of_value.explode()).sum(level=0) 
    return s.dot(s.T).div(s.sum(1)) 

%time method_quang(df)                                                                                                                                                                                                               
# CPU times: user 10.5 s, sys: 828 ms, total: 11.3 s
# Wall time: 11.3 s
# ...
# [12000 rows x 12000 columns]

경쟁자

def method_mcskinner(df):
    explode_df = df.set_index('id').list_of_value.explode().reset_index() 
    explode_df = explode_df.rename(columns={'list_of_value': 'value'}) 
    denom_df = explode_df.groupby('id').size().reset_index(name='denom') 
    numer_df = explode_df.merge(explode_df, on='value', suffixes=['', '_y']) 
    numer_df = numer_df.groupby(['id', 'id_y']).size().reset_index(name='numer') 
    calc_df = numer_df.merge(denom_df, on='id') 
    calc_df['score'] = calc_df['numer'] / calc_df['denom'] 
    return calc_df.pivot('id', 'id_y', 'score').fillna(0) 

%time method_mcskinner(df)
# CPU times: user 29.2 s, sys: 9.66 s, total: 38.9 s
# Wall time: 29.6 s
# ...
# [12000 rows x 12000 columns]
def method_rishab(df): 
    vals = [[len(set(val1) & set(val2)) / len(val1) for val2 in df['list_of_value']] for val1 in df['list_of_value']]
    return pd.DataFrame(columns=df['id'], data=vals)

%time method_rishab(df)                                                                                                                                                                                                              
# CPU times: user 2min 12s, sys: 4.64 s, total: 2min 17s
# Wall time: 2min 18s
# ...
# [12000 rows x 12000 columns]
def method_fahad(df): 
    ids = list(df['id']) 
    range_of_ids = range(len(ids)) 

    def score_calculation(s_id1,s_id2): 
        s1 = set(list(df.loc[df['id'] == ids[s_id1]]['list_of_value'])[0]) 
        s2 = set(list(df.loc[df['id'] == ids[s_id2]]['list_of_value'])[0]) 
        # Resultant calculation s1&s2 
        return round(len(s1&s2)/len(s1) , 2) 

    dic = {indexQFID:  [score_calculation(indexQFID,ind) for ind in range_of_ids] for indexQFID in range_of_ids} 
    return pd.DataFrame(dic) 

# Stopped manually after running for more than 10 minutes.

솔루션 세부 정보가 포함 된 원본 게시물

pandas자체 조인 으로이 작업을 수행 할 수 있습니다.

다른 답변에서 지적했듯이 첫 번째 단계는 데이터를 더 긴 형식으로 압축 해제하는 것입니다.

explode_df = df.set_index('id').list_of_value.explode().reset_index()
explode_df = explode_df.rename(columns={'list_of_value': 'value'})
explode_df
#     id value
# 0    0     a
# 1    0     b
# 2    0     c
# 3    1     d
# 4    1     b
# ...

이 표에서 ID 별 개수를 계산할 수 있습니다.

denom_df = explode_df.groupby('id').size().reset_index(name='denom')
denom_df
#    id  denom
# 0   0      3
# 1   1      3
# 2   2      3
# 3   3      3

그런 다음 자체 조인이 발생합니다. value 열에 . 교차하는 각 값에 대해 ID를 한 번 쌍으로 지정하므로 쌍을 이루는 ID를 계산하여 교차 크기를 얻을 수 있습니다.

numer_df = explode_df.merge(explode_df, on='value', suffixes=['', '_y'])
numer_df = numer_df.groupby(['id', 'id_y']).size().reset_index(name='numer')
numer_df
#     id  id_y  numer
# 0    0     0      3
# 1    0     1      2
# 2    0     2      3
# 3    0     3      3
# 4    1     0      2
# 5    1     1      3
# ...

그런 다음이 두 가지를 병합하여 점수를 계산할 수 있습니다.

calc_df = numer_df.merge(denom_df, on='id')
calc_df['score'] = calc_df['numer'] / calc_df['denom']
calc_df
#     id  id_y  numer  denom     score
# 0    0     0      3      3  1.000000
# 1    0     1      2      3  0.666667
# 2    0     2      3      3  1.000000
# 3    0     3      3      3  1.000000
# 4    1     0      2      3  0.666667
# 5    1     1      3      3  1.000000
# ...

행렬 형식을 선호하는 경우 a로 가능합니다 pivot. 데이터가 드문 경우 훨씬 더 큰 표현이됩니다.

calc_df.pivot('id', 'id_y', 'score').fillna(0)
# id_y         0         1         2         3
# id                                          
# 0     1.000000  0.666667  1.000000  1.000000
# 1     0.666667  1.000000  0.666667  0.666667
# 2     1.000000  0.666667  1.000000  1.000000
# 3     1.000000  0.666667  1.000000  1.000000

1

이 솔루션은 사용자 데이터의 크기와 값의 어떤 종류 효율적으로 작업 할 list말은 strint또는 다른, 또한 어떤 경우 반복 값을 돌보는.

# dummy data
df = pd.DataFrame({'id': [0, 1, 2, 3], 'list_of_value': [['a','b','c'],['d','b','c'], ['a','b','c'], ['a','b','c']]})
# calculating the target values using list comprehension
vals = [[len(set(val1) & set(val2)) / len(val1) for val2 in df['list_of_value']] for val1 in df['list_of_value']]
# new resultant Dataframe
df =  pd.DataFrame(columns=df['id'], data=vals)

이 경우, 목록 이해는 목록의 추가 속성을로드하고 각 반복에서 함수로 호출 할 필요가 없기 때문에 더 잘 수행됩니다. 다시 말해서, 일반적으로 함수의 프레임을 일시 중지하고 다시 시작하거나 다른 경우에는 여러 함수가 요청시 목록을 만드는 것보다 느리기 때문에 목록 이해가 더 빠릅니다.

목록을 작성하지 않는 루프 대신 목록 이해를 사용하면 의미없는 값의 목록을 무의식적으로 누적 한 다음 목록을 버리는 것이 목록을 작성하고 확장하는 오버 헤드로 인해 종종 느려집니다.

결과:

id         0         1         2         3
0   1.000000  0.666667  1.000000  1.000000
1   0.666667  1.000000  0.666667  0.666667
2   1.000000  0.666667  1.000000  1.000000
3   1.000000  0.666667  1.000000  1.000000

실행 시간:

import timeit

def function():
    df = pd.DataFrame({'id': [0, 1, 2, 3], 'list_of_value': [['a','b','c'],['d','b','c'], ['a','b','c'], ['a','b','c']]})
    vals = [[len(set(val1) & set(val2)) / len(val1) for val2 in df['list_of_value']] for val1 in df['list_of_value']]
    df =  pd.DataFrame(columns=df['id'], data=vals)

print(timeit.timeit(f'{function()}', number=1000000))
# 0.010986731999999999

0

리스트를 세트로 수렴하고 교차 기능을 사용하여 오버랩을 확인할 수 있습니다.

(당신이 요청 한대로 1 개의 적용 기능 만 사용됩니다 :-))

(
    df.assign(s = df.list_of_value.apply(set))
    .pipe(lambda x: pd.DataFrame([[len(e&f)/len(e) for f in x.s] for e in x.s]))
)

    0           1           2           3
0   1.000000    0.666667    1.000000    1.000000
1   0.666667    1.000000    0.666667    0.666667
2   1.000000    0.666667    1.000000    1.000000
3   1.000000    0.666667    1.000000    1.000000

0

product모든 조합을 얻는 데 사용 합니다. 그런 다음 numpy.isinand로 확인할 수 있습니다 numpy.mean.

from itertools import product
l = len(df)
new_df = pd.DataFrame(data = np.array(list(map(lambda arr: np.isin(*arr),
                                                product(df['list_of_value'],
                                                        repeat=2))))
                               .mean(axis=1).reshape(l,-1),
                      index = df['id'],
                      columns=df['id'])

id         0         1         2         3
id                                        
0   1.000000  0.666667  1.000000  1.000000
1   0.666667  1.000000  0.666667  0.666667
2   1.000000  0.666667  1.000000  1.000000
3   1.000000  0.666667  1.000000  1.000000

시간 샘플

%%timeit
l = len(df)
new_df = pd.DataFrame(data = np.array(list(map(lambda arr: np.isin(*arr),
                                                product(df['list_of_value'],
                                                        repeat=2))))
                               .mean(axis=1).reshape(l,-1),
                      index = df['id'],
                      columns=df['id'])
594 µs ± 5.05 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

0

빠르고, 또한 목록의 중복을 고려하십시오

... import itertools
... from collections import Counter
... a=df.list_of_value.tolist()
... l=np.array([len(Counter(x[0]) & Counter(x[1]))for x in [*itertools.product(a,a)]]).reshape(len(df),-1)
... out=pd.DataFrame(l/df.list_of_value.str.len().values[:,None],index=df.id,columns=df.id)
... 
out
id         0         1         2         3
id                                        
0   1.000000  0.666667  1.000000  1.000000
1   0.666667  1.000000  0.666667  0.666667
2   1.000000  0.666667  1.000000  1.000000
3   1.000000  0.666667  1.000000  1.000000

0

예! 우리는 답변에 주어진 직교 제품을 찾고 있습니다. 이것은 for 루프 나리스트 이해없이 달성 수 있습니다

데이터 프레임에 새로운 반복 값을 추가하여 df다음과 같이 보이도록하겠습니다 :

df['key'] = np.repeat(1, df.shape[0])
df

  list_of_values  key
0      [a, b, c]    1
1      [d, b, c]    1
2      [a, b, c]    1
3      [a, b, c]    1

다음으로 자체 병합

merged = pd.merge(df, df, on='key')[['list_of_values_x', 'list_of_values_y']]

병합 된 프레임은 다음과 같습니다.

   list_of_values_x list_of_values_y
0         [a, b, c]        [a, b, c]
1         [a, b, c]        [d, b, c]
2         [a, b, c]        [a, b, c]
3         [a, b, c]        [a, b, c]
4         [d, b, c]        [a, b, c]
5         [d, b, c]        [d, b, c]
6         [d, b, c]        [a, b, c]
7         [d, b, c]        [a, b, c]
8         [a, b, c]        [a, b, c]
9         [a, b, c]        [d, b, c]
10        [a, b, c]        [a, b, c]
11        [a, b, c]        [a, b, c]
12        [a, b, c]        [a, b, c]
13        [a, b, c]        [d, b, c]
14        [a, b, c]        [a, b, c]
15        [a, b, c]        [a, b, c]

그런 다음 각 행에 원하는 기능을 적용합니다. axis=1

values = merged.apply(lambda x: np.intersect1d(x[0], x[1]).shape[0] / len(x[1]), axis=1)

원하는 형식으로 값을 가져 오려면이 형식을 변경하십시오.

values.values.reshape(4, 4)
array([[1.        , 0.66666667, 1.        , 1.        ],
       [0.66666667, 1.        , 0.66666667, 0.66666667],
       [1.        , 0.66666667, 1.        , 1.        ],
       [1.        , 0.66666667, 1.        , 1.        ]])

도움이 되었기를 바랍니다 :)

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