bir sütun pandadan bir NxN matrisi oluşturma


11

her satırda bir liste değeri olan dataframe var.

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

bir satır ve diğer tüm satırlara karşı bir puan hesaplamak zorundayım

Örneğin:

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 0 ve id 1,2,3 arasında, tüm idler için benzer şekilde adım 2,3'ü tekrarlayın.

ve bir N x N veri çerçevesi oluşturmak; bunun gibi:

-  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

Şu anda benim kod döngü için sadece bir tane var:

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

Bunu yapmanın daha iyi bir yolu var mı? i for-loop yineleme yapmak yerine sadece bir uygulama işlevi yazabilir miyim. daha hızlı yapabilir miyim?


1
soruyu düzenledi, @Babydesta
Sriram Arvind Lakshmanakumar

1
6 değil, 0.6, sonuçta. boyut = 2, id.size = 3
Sriram Arvind Lakshmanakumar

Verileriniz ne kadar sürüyor? ve toplamda kaç değer oluşur list_of_value?
Quang Hoang

her list_of_value içinde en fazla 20 değer
Sriram Arvind Lakshmanakumar

Her birinde değil list_of_value. Toplamda, tüm satırlarda.
Quang Hoang

Yanıtlar:


7

Verileriniz çok büyük değilse get_dummies, değerleri kodlamak ve bir matris çarpımı yapmak için kullanabilirsiniz :

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

Çıktı:

          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

Güncelleme : İşte kod için kısa bir açıklama. Ana fikir, verilen listeleri bir sıcak kodlu haline dönüştürmektir:

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

Bunu elde ettikten sonra, iki satırın kesişme boyutu diyelim 0ve 1sadece onların nokta ürünüdür, çünkü bir karakter her iki satıra da aitse ve her ikisinde de temsil ediliyorsa 1.

Bunu göz önünde bulundurarak, ilk kullanım

df.list_of_value.explode()

her bir hücreyi bir seri haline getirmek ve tüm bu serileri birleştirmek. Çıktı:

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

Şimdi, pd.get_dummiesbu dizide onu bir sıcak kodlanmış veri çerçevesine dönüştürmek için kullanıyoruz:

   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

Gördüğünüz gibi, her bir değerin kendi satırı vardır. Aynı orijinal satıra ait olanları bir satıra birleştirmek istediğimizden, bunları yalnızca orijinal dizine göre toplayabiliriz. Böylece

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

istediğimiz ikili kodlanmış veri çerçevesini verir. Sonraki satır

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

mantığınız gibidir: s.dot(s.T)nokta ürünlerini satırlara göre hesaplar, ardından .div(s.sum(1))sayıları satırlara böler.


12k satır veri çerçevesi
Sriram Arvind Lakshmanakumar

@SriramArvindLakshmanakumar 12k satırlı, 12k x 12kveri çerçevesi ile sonuçlanır. Birkaç yüz benzersiz değeriniz varsa iyi olmalı.
Quang Hoang

kodu da açıklayabilir mi?
Sriram Arvind Lakshmanakumar

Tabii, ama işe yarıyor mu?
Quang Hoang

1
@SriramArvindLakshmanakumar Çözümümü kabul ettiğiniz için teşekkür ederiz. Açıklama ve düşünce mantığı için lütfen güncellemeye bakın.
Quang Hoang

3

Bunu dene

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)

Çıktı

     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

Ayrıca aşağıdaki gibi yapabilirsiniz

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

Küme listesinde iç içe liste kavrayışı kullanın s_list. Liste anlamada, intersectionçakışmayı kontrol etmek ve her sonucun uzunluğunu almak için işlemi kullanın. Son olarak, veri çerçevesini oluşturun ve her bir listenin uzunluğuna bölün.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

Her listede yinelenen değerler varsa, collections.Counteryerine kullanmalısınız set. Örnek veri kimliği = 0 olarak ['a','a','c']ve kimlik = 1 olarak değiştirdim['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

Güncellenmiş

Önerilen çok sayıda aday çözüm olduğundan, zamanlama analizi yapmak iyi bir fikir gibi görünüyor. OP tarafından istendiği gibi 12k satırlı bazı rastgele veriler oluşturdum, set başına 3 elementi korudum, ancak kümeleri doldurmak için mevcut alfabenin boyutunu genişlettim. Bu, gerçek verilere uyacak şekilde ayarlanabilir.

Test edilmesini veya güncellenmesini istediğiniz bir çözümünüz varsa bana bildirin.

Kurmak

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

Mevcut Kazanan

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]

Yarışmacını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.

Çözüm ayrıntılarına sahip orijinal gönderi

Bunu pandaskendi kendine katılma ile yapmak mümkündür .

Diğer cevapların işaret ettiği gibi, ilk adım verileri daha uzun bir forma açmaktır.

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

Bu tablodan, kimlik başına sayıları hesaplamak mümkündür.

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

Ve sonra valuesütunda gerçekleşen kendi kendine birleştirme gelir . Bu, her kesişen değer için kimlikleri bir kez eşleştirir, böylece eşleştirilmiş kimlikler kesişim boyutlarını elde etmek için sayılabilir.

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

Bu ikisi daha sonra birleştirilebilir ve bir skor hesaplanabilir.

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

Matris formunu tercih ederseniz, bu a ile mümkündür pivot. Veriler seyrekse bu çok daha büyük bir gösterim olacaktır.

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

Bu çözüm, verilerin herhangi bir boyut ve değerlerin her türlü verimli çalışacak listsöz hakkından onun strya intveya başka da varsa tekrarlayan değerlerin bakımı.

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

Bu durumda, Liste kavrama daha iyi performans gösterir, listenin append özniteliğini yüklemesi ve her yinelemede bir işlev olarak çağırması gerekmez. Diğer bir deyişle ve genel olarak, bir işlevin çerçevesini askıya alma ve sürdürme veya diğer durumlarda birden çok işlev isteğe bağlı bir liste oluşturmaktan daha yavaş olduğu için liste kavrayışları daha hızlı çalışır.

Liste oluşturmayan bir döngü yerine liste anlamayı kullanmak, anlamsız değerlerin bir listesini saçma bir şekilde biriktirmek ve sonra listeyi atmak, listeyi oluşturma ve genişletme yükü nedeniyle genellikle daha yavaştır.

Sonuç:

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

Uygulama vakti:

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

Listeyi bir kümeye dönüştürebilir ve çakışma olup olmadığını kontrol etmek için kavşak fonksiyonunu kullanabilirsiniz:

(sadece 1 uygulama fonksiyonu sorduğunuz gibi :-))

(
    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

productTüm kombinasyonları elde etmek için kullanırdım . Sonra kontrol edebilir numpy.isinve 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

Zaman örneği

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

Hızlı olmalı, listedeki kopyayı da düşünün

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

Evet! Burada bu cevapta verilen Kartezyen bir ürün arıyoruz . Bu, for döngüsü veya liste kavrama olmadan elde edilebilir

Veri çerçevemize dfşu şekilde görünmesi için tekrarlanan yeni bir değer ekleyelim :

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

Kendisi ile bir sonraki birleşme

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

Birleştirilmiş çerçeve şöyle görünür:

   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]

Sonra her satıra istenen fonksiyonu kullanarak axis=1

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

İstenen formattaki değerleri almak için bunu yeniden şekillendirme

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

Bu yardımcı olur umarım :)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.