tạo ma trận NxN từ một cột gấu trúc


11

tôi có dataframe với mỗi hàng có giá trị danh sách.

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

tôi phải tính điểm với một hàng và so với tất cả các hàng khác

Ví dụ:

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

lặp lại bước 2,3 giữa id 0 và id 1,2,3, tương tự cho tất cả các id.

và tạo một khung dữ liệu N x N; chẳng hạn như thế này:

-  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

Ngay bây giờ mã của tôi chỉ có một vòng lặp:

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

Có cách nào tốt hơn để làm điều này? Tôi chỉ có thể viết một hàm áp dụng thay vì thực hiện một vòng lặp for-loop. tôi có thể làm cho nó nhanh hơn?


1
đã chỉnh sửa câu hỏi, @Babydesta
Sriram Arvind Lakshmanakumar

1
nó không phải là 6, nó là 0,6, resultant.size = 2, id.size = 3
Sriram Arvind Lakshmanakumar

Dữ liệu của bạn dài bao nhiêu? và hoàn toàn có bao nhiêu giá trị xảy ra trong list_of_value?
Quang Hoàng

tối đa 20 giá trị trong mỗi danh sách_of_value
Sriram Arvind Lakshmanakumar

Không phải trong mỗi list_of_value. Ý tôi là tổng cộng, trên tất cả các hàng.
Quang Hoàng

Câu trả lời:


7

Nếu dữ liệu của bạn không quá lớn, bạn có thể sử dụng get_dummiesđể mã hóa các giá trị và thực hiện phép nhân ma trận:

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

Đầu ra:

          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

Cập nhật : Đây là một lời giải thích ngắn cho mã. Ý tưởng chính là biến các danh sách đã cho thành một mã hóa nóng:

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

Khi chúng ta có điều đó, kích thước giao điểm của hai hàng, giả sử, 01chỉ là sản phẩm dấu chấm của chúng, bởi vì một ký tự thuộc cả hai hàng khi và chỉ khi nó được biểu thị bằng 1cả hai.

Với ý nghĩ đó, lần đầu tiên sử dụng

df.list_of_value.explode()

để biến mỗi ô thành một chuỗi và nối tất cả các chuỗi đó. Đầu ra:

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

Bây giờ, chúng tôi sử dụng pd.get_dummiestrên chuỗi đó để biến nó thành một khung dữ liệu được mã hóa một lần:

   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

Như bạn có thể thấy, mỗi giá trị có một hàng riêng. Vì chúng tôi muốn kết hợp những thứ thuộc về cùng một hàng ban đầu thành một hàng, chúng tôi chỉ có thể tính tổng chúng theo chỉ mục ban đầu. Như vậy

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

đưa ra khung dữ liệu được mã hóa nhị phân mà chúng ta muốn. Dòng tiếp theo

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

cũng giống như logic của bạn: s.dot(s.T)tính các sản phẩm chấm theo hàng, sau đó .div(s.sum(1))chia số đếm theo hàng.


Khung dữ liệu 12k hàng
Sriram Arvind Lakshmanakumar

@SriramArvindLakshmanakumar với 12k hàng, bạn sẽ kết thúc với 12k x 12kdataframe. Sẽ ổn nếu bạn có khoảng vài trăm giá trị duy nhất.
Quang Hoàng

cũng có thể giải thích mã?
Sriram Arvind Lakshmanakumar

Chắc chắn, nhưng nó hoạt động?
Quang Hoàng

1
@SriramArvindLakshmanakumar Cảm ơn bạn đã chấp nhận giải pháp của tôi. Xin vui lòng xem cập nhật cho một lời giải thích và suy nghĩ logic.
Quang Hoàng

3

Thử cái này

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)

Đầu ra

     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

Bạn cũng có thể làm như sau

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ử dụng hiểu danh sách lồng nhau trong danh sách các tập hợp s_list. Trong phạm vi hiểu danh sách, sử dụng intersectionthao tác để kiểm tra chồng chéo và nhận độ dài của từng kết quả. Cuối cùng, xây dựng khung dữ liệu và chia nó theo độ dài của mỗi danh sách trongdf.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

Trong trường hợp có các giá trị trùng lặp trong mỗi danh sách, bạn nên sử dụng collections.Counterthay vì set. Tôi đã thay đổi dữ liệu mẫu id = 0 thành ['a','a','c']và id = 1 thành['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

Đã cập nhật

Vì có rất nhiều giải pháp ứng cử viên được đề xuất, có vẻ như là một ý tưởng tốt để thực hiện phân tích thời gian. Tôi đã tạo một số dữ liệu ngẫu nhiên với 12k hàng theo yêu cầu của OP, giữ nguyên 3 yếu tố trên mỗi bộ nhưng mở rộng kích thước của bảng chữ cái có sẵn để điền vào các bộ. Điều này có thể được điều chỉnh để phù hợp với dữ liệu thực tế.

Hãy cho tôi biết nếu bạn có một giải pháp mà bạn muốn thử nghiệm hoặc cập nhật.

Thiết lập

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

Người chiến thắng hiện tại

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]

Ứng cử viên

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.

Bài gốc với chi tiết giải pháp

Có thể làm điều này pandasvới một người tự tham gia.

Như các câu trả lời khác đã chỉ ra, bước đầu tiên là giải nén dữ liệu thành dạng dài hơn.

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

Từ bảng này, có thể tính tổng số trên mỗi 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

Và sau đó đến việc tự tham gia, xảy ra trên valuecột. Cặp ID này một lần cho mỗi giá trị giao nhau, vì vậy ID được ghép có thể được tính để có kích thước giao nhau.

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

Hai cái này sau đó có thể được hợp nhất và tính điểm.

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

Nếu bạn thích dạng ma trận, điều đó là có thể với a pivot. Đây sẽ là một đại diện lớn hơn nhiều nếu dữ liệu thưa thớt.

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

Giải pháp này sẽ hoạt động hiệu quả với bất kỳ kích thước dữ liệu nào và bất kỳ loại giá trị nào theo cách listnói của bạn strhoặc nói intcách khác, cũng quan tâm đến các giá trị lặp đi lặp lại nếu có.

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

Trong trường hợp này, việc hiểu danh sách hoạt động tốt hơn là vì nó không cần tải thuộc tính chắp thêm của danh sách và gọi nó là một hàm tại mỗi lần lặp. Nói cách khác và nói chung, việc hiểu danh sách thực hiện nhanh hơn vì việc tạm dừng và tiếp tục khung của một chức năng hoặc nhiều chức năng trong các trường hợp khác chậm hơn so với việc tạo danh sách theo yêu cầu.

Sử dụng một sự hiểu biết danh sách thay cho một vòng lặp không xây dựng một danh sách, việc tích lũy một cách vô lý một danh sách các giá trị vô nghĩa và sau đó ném danh sách đi, thường chậm hơn vì quá trình tạo và mở rộng danh sách.

Kết quả:

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

Thời gian thực hiện:

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

Bạn có thể hội tụ danh sách thành một tập hợp và sử dụng chức năng giao nhau để kiểm tra sự chồng chéo:

(chỉ có 1 chức năng áp dụng được sử dụng như bạn đã hỏi :-))

(
    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

Tôi sẽ sử dụng productđể có được tất cả các kết hợp. Sau đó chúng ta có thể kiểm tra với numpy.isinnumpy.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

Mẫu thời gian

%%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

Nên nhanh, cũng xem xét các bản sao trong danh sách

... 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

Đúng! Chúng tôi đang tìm kiếm một sản phẩm của Cartesian ở đây, được đưa ra trong câu trả lời này . Điều này có thể đạt được mà không cần vòng lặp for hoặc hiểu danh sách

Hãy thêm một giá trị lặp lại mới vào khung dữ liệu của chúng tôi dfđể nó trông như thế này:

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

Hợp nhất tiếp theo với chính nó

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

Đây là cách khung được hợp nhất trông như sau:

   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]

Sau đó, chúng tôi áp dụng chức năng mong muốn cho mỗi hàng bằng cách sử dụng axis=1

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

Định hình lại điều này để có được các giá trị ở định dạng mong muốn

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.        ]])

Hi vọng điêu nay co ich :)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.