สร้างเมทริกซ์ NxN จากหมีแพนด้าหนึ่งคอลัมน์


11

ฉันมี dataframe โดยแต่ละแถวมีค่ารายการ

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

ทำซ้ำขั้นตอนที่ 2,3 ระหว่าง id 0 และ id 1,2,3 เช่นเดียวกันกับรหัสทั้งหมด

และสร้าง dataframe 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

ตอนนี้รหัสของฉันมีเพียงหนึ่งวงเท่านั้น:

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-loop ฉันจะทำให้เร็วขึ้นได้ไหม


1
แก้ไขคำถาม @Babydesta
Sriram Arvind Lakshmanakumar

1
มันไม่ใช่ 6, คือ 0.6, resultant.size = 2, id.size = 3
Sriram Arvind Lakshmanakumar

ข้อมูลของคุณนานเท่าไหร่ และทั้งหมดวิธีการหลายค่าเกิดขึ้นในlist_of_value?
Quang Hoang

สูงสุด 20 ค่าในแต่ละ list_of_value
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

เมื่อเรามีขนาดของการตัดกันของสองแถวพูด0และ1เป็นเพียงแค่ผลิตภัณฑ์จุดของพวกเขาเพราะตัวละครเป็นของทั้งสองแถวถ้าและเพียงถ้ามันเป็นตัวแทนของ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)

ให้ dataframe ที่เข้ารหัสแบบไบนารีที่เราต้องการ บรรทัดถัดไป

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

เป็นเพียงตรรกะของคุณ: s.dot(s.T)คำนวณผลิตภัณฑ์ดอทโดยแถวแล้ว.div(s.sum(1))หารนับด้วยแถว


12k dataframe row
Sriram Arvind Lakshmanakumar

@SriramArvindLakshmanakumar ที่มี 12k แถวคุณจะจบลงด้วย12k x 12kdataframe ควรจะดีถ้าคุณมีค่าที่ไม่ซ้ำกันประมาณสองสามร้อย
Quang Hoang

สามารถอธิบายรหัสได้หรือไม่
Sriram Arvind Lakshmanakumar

แน่นอน แต่มันใช้งานได้?
Quang Hoang

1
@SiriramArvindLakshmanakumar ขอบคุณที่ยอมรับโซลูชันของฉัน โปรดดูการปรับปรุงสำหรับคำอธิบายและตรรกะความคิด
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

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

Updated

เนื่องจากมีการเสนอวิธีแก้ปัญหาผู้สมัครจำนวนมากดูเหมือนว่าเป็นความคิดที่ดีที่จะทำการวิเคราะห์เวลา ฉันสร้างข้อมูลสุ่มบางตัวที่มีแถว 12k ตามที่ร้องขอโดย OP โดยรักษา 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
# ...

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การพูดของตนstrหรือintหรือมิฉะนั้นยังมีการดูแลค่าซ้ำ ๆ ถ้ามี

# 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)

ในกรณีนี้ความเข้าใจในรายการมีประสิทธิภาพดีกว่าเพราะไม่จำเป็นต้องโหลดแอตทริบิวต์ต่อท้ายของรายการและเรียกว่าเป็นฟังก์ชันในการทำซ้ำแต่ละครั้ง ในคำอื่น ๆ และโดยทั่วไปรายการความเข้าใจจะทำงานได้เร็วขึ้นเนื่องจากการระงับและกลับมาทำงานเฟรมของฟังก์ชันหรือฟังก์ชั่นหลายรายการในกรณีอื่นช้ากว่าการสร้างรายการตามต้องการ

การใช้ list comprehension แทน loop ที่ไม่ได้สร้าง list การรวบรวมรายการของค่าที่ไม่มีความหมายแล้วทิ้งรายการนั้นไปบ่อยครั้งนั้นช้ากว่าเพราะค่าใช้จ่ายในการสร้างและขยายรายการ

ผลลัพธ์:

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.isinและ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

ใช่ เรากำลังมองหาผลิตภัณฑ์ Cartesian ที่นี่ซึ่งจะได้รับในครั้งนี้คำตอบ สิ่งนี้สามารถทำให้สำเร็จได้โดยไม่ต้องใช้ for loop หรือ list

ลองเพิ่มค่าซ้ำใหม่ในกรอบข้อมูลของเรา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.