Подсчитайте последовательные элементы в списке фрейма данных на уровне ячеек в новых столбцах

#python-3.x #pandas #list #dataframe

Вопрос:

У меня есть следующий df:

 df6 = pd.DataFrame({'name':['Sara',  'John', 'Jack'],
                   'places': ['UK,UK,UK,UK,US,CA', 'US,US,US,CA,CA,CA', 'Mexico,AUS,AUS,Mexico,Mexico']
                   })

df6
 

Выглядит как:

     name    places
0   Sara    UK,UK,UK,UK,US,CA
1   John    US,US,US,CA,CA,CA
2   Jack    Mexico,AUS,AUS,Mexico,Mexico
 

Колонка «Места» посвящена только 5 странам. То, что я пытаюсь сделать, — это найти количество последовательных визитов в каждую страну. Таким образом, в основном результат будет таким:

     name    UK   US   CA   Mexico   AUS    
0   Sara    4    0    0       0      0
1   John    0    3    3       0      0  
2   Jack    0    0    0       2      2
 

Шаги, которые я сделал до сих пор, это:

 df6['consecutive'] = df6.places.map(lambda x: [Counter(group[1]) for group in groupby(x.split(','))])
 

Это дает мне list of dicts :

     name    places                        consecutive
0   Sara    UK,UK,UK,UK,US,CA             [{'UK': 4}, {'US': 1}, {'CA': 1}]
1   John    US,US,US,CA,CA,CA             [{'US': 3}, {'CA': 3}]
2   Jack    Mexico,AUS,AUS,Mexico,Mexico  [{'Mexico': 1}, {'AUS': 2}, {'Mexico': 2}]
 

Теперь я остановился здесь на том, как перебирать каждую ячейку в последовательном столбце, чтобы найти values > 1 каждую ячейку и преобразовать df6 в конечный результат:

     name    UK   US   CA   Mexico   AUS    
0   Sara    4    0    0       0      0
1   John    0    3    3       0      0  
2   Jack    0    0    0       2      2
 

Комментарии:

1. Вы просто берете максимальное последовательное значение или последнее? У Джека есть Мексика 1 и Мексика 2.

2. Значения > 1, потому что в моих данных, если значение равно 1, это означает только одно посещение, поэтому для Джека я выбираю Мексику 2 и Австралию 2

3. Да, но если бы у тебя был Джек Mexico, Mexico, Mexico, AUS, AUS, Mexico, Mexico , что бы ты оставил себе ?

Ответ №1:

Вы можете использовать pd.crosstab :

 df6["places"] = df6["places"].apply(lambda x: x.split(","))
df6 = df6.explode("places")

out = pd.crosstab(df6["name"], df6["places"])
out.index.name = None
out.columns.name = None
print(out)
 

С принтами:

       AUS  CA  Mexico  UK  US
Jack    2   0       3   0   0
John    0   3       0   0   3
Sara    0   1       0   4   1
 

ИЗМЕНИТЬ: Для consecutive столбца суммы (для последовательных значений > 1>):

 from itertools import groupby
from collections import Counter

df6["consecutive"] = df6.places.map(
    lambda x: [
        {k: v for k, v in Counter(group[1]).items() if v > 1}
        for group in groupby(x.split(","))
    ]
)

df6 = df6.explode("consecutive").reset_index(drop=True)
out = (
    pd.concat([df6, pd.DataFrame(df6.pop("consecutive").tolist())], axis=1)
    .groupby("name")
    .sum()
)
print(out)
 

С принтами:

        UK   US   CA  AUS  Mexico
name                            
Jack  0.0  0.0  0.0  2.0     2.0
John  0.0  3.0  3.0  0.0     0.0
Sara  4.0  0.0  0.0  0.0     0.0
 

Комментарии:

1. Спасибо, Андрей, ваши выходные данные показывают все визиты в каждую страну. Я ищу способ найти только consecutive visits это, поэтому я использовал df6['consecutive'] = df6.places.map(lambda x: [Counter(group[1]) for group in groupby(x.split(','))]) для поиска последовательных значений на основе отсортированного списка, разделенного запятыми, в столбце places

Ответ №2:

Мы можем str.split и explode places . Затем воспользуйтесь groupby size фильтром и, unstack чтобы получить количество последовательных loc посещений, включающим только посещения, превышающие 1 последовательное. Затем groupby sum сократить до одной строки на имя и join вернуться к исходному кадру данных:

 places = df6["places"].str.split(',').explode()  # Each place in own row

df7 = df6[['name']].join(
    places.groupby(
        [df6['name'],  # Name
         places,  # Places
         # consecutive duplicates in separate groups
         places.ne(places.shift()).groupby(df6['name']).cumsum()]
    ).size()  # Count how many in each group
        .loc[lambda x: x > 1]  # Filter to include only > 1 visits
        .unstack(1, fill_value=0)  # Make places columns
        .groupby(level=0).sum(),  # Get single row per name
    on='name'  # join back on name column
)
 

df7 :

    name  AUS  CA  Mexico  UK  US
0  Sara    0   0       0   4   0
1  John    0   3       0   0   3
2  Jack    2   0       2   0   0
 

Ответ №3:

Или вы можете использовать сводную таблицу:

 import pandas as pd

df6 = pd.DataFrame({'name':['Sara',  'John', 'Jack'],
                   'places': ['UK,UK,UK,UK,US,CA', 'US,US,US,CA,CA,CA', 'Mexico,AUS,AUS,Mexico,Mexico']
               })

df6['places'] = df6.places.str.split(',')
df6 = df6.explode('places')
df6['lag_places'] = df6.places.shift(1)
df6 = df6.query('places == lag_places').pivot_table(index = 'name', columns = 'places',  aggfunc = 'count')
df6.loc[:, df6.columns != 'places'] = df6.loc[:, df6.columns != 'places'].apply(lambda x: x 1) # add 1 according to your definition
df6.columns = [x[1] for x in df6.columns]
df6.fillna(0, inplace = True)

#      AUS   CA  Mexico   UK   US
#name                            
#Jack  2.0  0.0     2.0  0.0  0.0
#John  0.0  3.0     0.0  0.0  3.0
#Sara  0.0  0.0     0.0  4.0  0.0