Свернуть 3 столбца текста в 1 в широком фрейме данных Pandas - PullRequest
0 голосов
/ 13 июня 2018

У меня есть набор данных с одним типом данных, распределенный по нескольким столбцам.Я хотел бы уменьшить их до одного столбца.У меня есть функция, которая выполняет это, но это громоздкий процесс, и я надеюсь, что есть более чистый способ сделать это.Вот игрушечный пример моих данных:

UID    COMPANY    EML    MAI   TEL
273    7UP        nan    nan   TEL
273    7UP        nan    MAI   nan
906    WSJ        nan    nan   TEL
906    WSJ        EML    nan   nan
736    AIG        nan    MAI   nan

Что я хотел бы получить:

UID    COMPANY   CONTACT_INFO
273    7UP       MT
906    WSJ       ET
736    AIG       M

Я решил это, написав функцию, которая преобразует EML,MAI или TEL в простое число, агрегирует результаты, а затем преобразует сумму в составляющие типы контактов.Это работает и довольно быстро.Вот пример:

def columnRedux(df):
    newDF = df.copy()
    newDF.fillna('-', inplace=True)
    newDF['CONTACT_INFO'] = newDF['EML'] + newDF['MAI'] + newDF['TEL']
    newDF.replace('EML--', 7, inplace=True)
    newDF.replace('-MAI-', 101, inplace=True)
    newDF.replace('--TEL', 1009, inplace=True)

    small = newDF.groupby(['UID', 'COMPANY'], as_index=False)['CONTACT_INFO'].sum()

    small.replace(7, 'E', inplace=True)
    small.replace(101, 'M', inplace=True)
    small.replace(108, 'EM', inplace=True)
    small.replace(1009, 'T', inplace=True)
    small.replace(1016, 'ET', inplace=True)
    small.replace(1110, 'MT', inplace=True)
    small.replace(1117, 'EMT', inplace=True)

    return small

df1 = pd.DataFrame(
    {'EML' : [np.nan, np.nan, np.nan, 'EML', np.nan, np.nan, 'EML', np.nan, np.nan, 'EML', 'EML', np.nan],
    'MAI' : [np.nan, 'MAI', np.nan, np.nan, 'MAI', np.nan, np.nan, np.nan, 'MAI', np.nan, np.nan, 'MAI'],
    'COMPANY' : ['7UP', '7UP', 'UPS', 'UPS', 'UPS', 'WSJ', 'WSJ', 'TJX', 'AIG', 'CDW', 'HEB', 'HEB'],
    'TEL' : ['TEL', np.nan, 'TEL', np.nan, np.nan, 'TEL', np.nan, 'TEL', np.nan, np.nan, np.nan, np.nan],
    'UID' : [273, 273, 865, 865, 865, 906, 906, 736, 316, 458, 531, 531]},
    columns=['UID', 'COMPANY', 'EML', 'MAI', 'TEL'])

cleanDF = columnRedux(df1)

Моя проблема в том, что у меня есть несколько наборов данных, каждый со своим собственным набором "широких" столбцов.У некоторых есть 5+ столбцов, которые будут уменьшены.Жесткое кодирование преобразований для всех вариантов не является тривиальным.Есть ли более чистый способ сделать это?

Ответы [ 3 ]

0 голосов
/ 13 июня 2018

Давайте попробуем это:

(df1.set_index(['UID','COMPANY']).notnull() * df1.columns[2:].str[0])\
.sum(level=[0,1]).sum(1).reset_index(name='CONTACT_INFO')

Вывод:

   UID COMPANY CONTACT_INFO
0  273     7UP           MT
1  865     UPS          EMT
2  906     WSJ           ET
3  736     TJX            T
4  316     AIG            M
5  458     CDW            E
6  531     HEB           EM

Разделение для @AntonvBR:

df2 = df1.set_index(['UID','COMPANY'])
df_out  = ((df2.notnull() * df2.columns.str[0])
           .sum(level=[0,1]) #consolidate rows of contact info to one line
           .sum(1)  #sum across columns to create one column
           .reset_index(name='CONTACT_INFO'))
print(df_out)

Вывод:

   UID COMPANY CONTACT_INFO
0  273     7UP           MT
1  865     UPS          EMT
2  906     WSJ           ET
3  736     TJX            T
4  316     AIG            M
5  458     CDW            E
6  531     HEB           EM
0 голосов
/ 13 июня 2018

Используя dot создать новые столбцы после groupby first

s=df.groupby(['UID','COMPANY'],as_index=False).first()

s['CONTACT_INFO']=s[['EML','MAI','TEL']].notnull().dot(s.columns[2:].str[0])
s.dropna(1)
Out[349]: 
   UID COMPANY CONTACT_INFO
0  273     7UP           MT
1  736     AIG            M
2  906     WSJ           ET
0 голосов
/ 13 июня 2018

Может быть, не самое приятное решение.Но можно было бы использовать простую группировку и обусловить включенные элементы:

df = df.groupby(['UID','COMPANY'])[['EML','MAI','TEL']]\
    .apply(lambda x: ''.join(sorted([i[0] for y in x.values for i in y if pd.notnull(i)])))\
    .reset_index()\
    .rename(columns={0:'CONTACT_INFO'})

Или альтернативой было бы преобразовать сгруппированные кадры данных в тип str и заменить строки и сумму.Вполне читабельно, я бы сказал.

m = {
    'nan':'',
    'EML':'E',
    'MAI':'M',
    'TEL':'T'
}

df = df.groupby(['UID','COMPANY'])[['EML','MAI','TEL']]\
       .apply(lambda x: x.astype(str).replace(m).sum().sum())\
       .reset_index()\
       .rename(columns={0:'CONTACT_INFO'})

Полный пример:

import pandas as pd
import numpy as np

data = '''\
UID    COMPANY    EML    MAI   TEL
273    7UP        nan    nan   TEL
273    7UP        nan    MAI   nan
906    WSJ        nan    nan   TEL
906    WSJ        EML    nan   nan
736    AIG        nan    MAI   nan'''

fileobj = pd.compat.StringIO(data)
df = pd.read_csv(fileobj, sep='\s+').replace('NaN',np.nan)

# use a nested list comprehension to flatten the array and remove nans.
df = df.groupby(['UID','COMPANY'])[['EML','MAI','TEL']]\
    .apply(lambda x: ''.join(sorted([i[0] for y in x.values for i in y if pd.notnull(i)])))\
    .reset_index()\
    .rename(columns={0:'CONTACT_INFO'})

print(df)

Возвращает:

UID  COMPANY  CONTACT_INFO
273      7UP            MT
736      AIG             M
906      WSJ            ET
dtype: object
...