buat matriks NxN dari satu panda kolom

11

Saya punya dataframe dengan setiap baris memiliki nilai daftar.

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

saya harus menghitung skor dengan satu baris dan terhadap semua baris lainnya

Untuk misalnya:

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

ulangi langkah 2,3 antara id 0 dan id 1,2,3, sama untuk semua id.

dan membuat bingkai data N x N; seperti ini:

-  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

Saat ini kode saya hanya memiliki satu untuk loop:

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

Apakah ada cara yang lebih baik untuk melakukan ini? bisakah saya hanya menulis satu fungsi yang berlaku alih-alih melakukan iterasi for-loop. dapatkah saya membuatnya lebih cepat?

Sriram Arvind Lakshmanakumar
sumber
1
mengedit pertanyaan, @Babydesta
Sriram Arvind Lakshmanakumar
1
itu bukan 6, itu 0,6, resultant.size = 2, id.size = 3
Sriram Arvind Lakshmanakumar
Berapa lama data Anda? dan berapa banyak nilai yang terjadi list_of_value?
Quang Hoang
maksimum 20 nilai di setiap list_of_value
Sriram Arvind Lakshmanakumar
Tidak di masing-masing list_of_value. Maksud saya total, di semua baris.
Quang Hoang

Jawaban:

7

Jika data Anda tidak terlalu besar, Anda bisa menggunakannya get_dummiesuntuk menyandikan nilai dan melakukan perkalian matriks:

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

Keluaran:

          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

Pembaruan : Berikut ini penjelasan singkat untuk kodenya. Gagasan utamanya adalah mengubah daftar yang diberikan menjadi satu-hot-encoded:

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

Setelah kita memiliki itu, ukuran persimpangan dari dua baris, katakan, 0dan 1hanya produk titik mereka, karena karakter milik kedua baris jika dan hanya jika diwakili oleh 1keduanya.

Dengan pemikiran itu, gunakan pertama kali

df.list_of_value.explode()

untuk mengubah setiap sel menjadi seri dan menggabungkan semua seri itu. Keluaran:

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

Sekarang, kami menggunakan pd.get_dummiespada seri itu untuk mengubahnya menjadi kerangka data satu-panas-disandikan:

   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

Seperti yang Anda lihat, setiap nilai memiliki barisnya sendiri. Karena kami ingin menggabungkan mereka yang memiliki baris asli yang sama ke satu baris, kami dapat menjumlahkannya dengan indeks asli. Jadi

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

memberikan dataframe binary-encoded yang kita inginkan. Baris selanjutnya

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

sama seperti logika Anda: s.dot(s.T)menghitung titik produk dengan baris, lalu .div(s.sum(1))membagi jumlah dengan baris.

Quang Hoang
sumber
Baris data 12k
Sriram Arvind Lakshmanakumar
@SriramArvindLakshmanakumar dengan baris 12k, Anda akan berakhir dengan 12k x 12kkerangka data. Seharusnya tidak masalah jika Anda memiliki sekitar beberapa ratus nilai unik.
Quang Hoang
dapat menjelaskan kode juga?
Sriram Arvind Lakshmanakumar
Tentu, tetapi apakah itu berhasil?
Quang Hoang
1
@SriramArvindLakshmanakumar Terima kasih telah menerima solusi saya. Silakan lihat pembaruan untuk penjelasan dan logika pemikiran.
Quang Hoang
3

Coba ini

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)

Keluaran

     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

Anda juga dapat melakukannya sebagai berikut

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)
FAHAD SIDDIQUI
sumber
2

Gunakan pemahaman daftar bersarang pada daftar set s_list. Dalam pemahaman daftar, gunakan intersectionoperasi untuk memeriksa tumpang tindih dan mendapatkan panjang setiap hasil. Terakhir, buat kerangka data dan bagilah dengan panjang masing-masing daftardf.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

Dalam hal terdapat nilai-nilai duplikat di setiap daftar, Anda harus menggunakan collections.Counterbukan set. Saya mengubah sampel data id = 0 ke ['a','a','c']dan id = 1 ke['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
Andy L.
sumber
2

Diperbarui

Karena ada banyak solusi kandidat yang diusulkan, sepertinya ide yang baik untuk melakukan analisis waktu. Saya menghasilkan beberapa data acak dengan baris 12k seperti yang diminta oleh OP, tetap dengan 3 elemen per set tetapi memperluas ukuran alfabet yang tersedia untuk mengisi set. Ini dapat disesuaikan agar sesuai dengan data aktual.

Beri tahu saya jika Anda memiliki solusi yang ingin Anda uji atau perbarui.

Mendirikan

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

Pemenang saat ini

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]

Peserta

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.

Posting asli dengan detail solusi

Dimungkinkan untuk melakukan ini pandasdengan bergabung sendiri.

Seperti yang ditunjukkan oleh jawaban lain, langkah pertama adalah membongkar data menjadi bentuk yang lebih panjang.

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

Dari tabel ini dimungkinkan untuk menghitung jumlah per-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

Dan kemudian muncul self-join, yang terjadi pada valuekolom. Ini memasangkan ID satu kali untuk setiap nilai perpotongan, sehingga ID yang berpasangan dapat dihitung untuk mendapatkan ukuran persimpangan.

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

Keduanya kemudian dapat digabungkan, dan skor dihitung.

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

Jika Anda lebih suka bentuk matriks, itu dimungkinkan dengan a pivot. Ini akan menjadi representasi yang jauh lebih besar jika datanya jarang.

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
mcskinner
sumber
1

Solusi ini akan bekerja secara efisien dengan ukuran data apa pun dan segala jenis nilai dalam Anda listkatakan stratau intatau sebaliknya, juga menjaga nilai berulang jika ada.

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

Dalam hal ini, Pemahaman daftar berkinerja lebih baik karena tidak perlu memuat atribut append dari daftar dan menyebutnya sebagai fungsi di setiap iterasi. Dengan kata lain dan secara umum, pemahaman daftar berkinerja lebih cepat karena menangguhkan dan melanjutkan bingkai fungsi, atau beberapa fungsi dalam kasus lain lebih lambat daripada membuat daftar sesuai permintaan.

Menggunakan pemahaman daftar sebagai ganti loop yang tidak membangun daftar, secara nonsensik mengumpulkan daftar nilai yang tidak berarti dan kemudian membuang daftar itu, seringkali lebih lambat karena overhead menciptakan dan memperluas daftar.

Hasil:

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

Waktu eksekusi:

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
Rishab P.
sumber
0

Anda dapat mengubah daftar menjadi satu set dan menggunakan fungsi persimpangan untuk memeriksa tumpang tindih:

(hanya 1 fungsi yang digunakan saat Anda bertanya :-))

(
    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
Allen
sumber
0

Saya akan gunakan productuntuk mendapatkan semua kombinasi. Kemudian kita dapat memeriksa numpy.isindan 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

Sampel waktu

%%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)
ansev
sumber
0

Harus cepat, pertimbangkan juga duplikat dalam daftar

... 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
YOBEN_S
sumber
0

Iya! Kami sedang mencari produk Cartesian di sini, yang diberikan dalam jawaban ini . Ini dapat dicapai tanpa untuk loop atau pemahaman daftar

Mari kita tambahkan nilai berulang baru ke bingkai data kita dfsehingga terlihat seperti ini:

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

Selanjutnya bergabung dengan dirinya sendiri

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

Beginilah tampilan bingkai yang digabungkan:

   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]

Kemudian kami menerapkan fungsi yang diinginkan untuk setiap baris menggunakan axis=1

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

Membentuk ulang ini untuk mendapatkan nilai dalam format yang diinginkan

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

Semoga ini membantu :)

Pushkar Nimkar
sumber