Pandas: как объединить два фрейма данных в столбце, сохранив информацию первого?

#python #pandas

#python #pandas #фрейм данных

Вопрос:

У меня есть два фрейма данных df1 и df2 . df1 содержит информацию о возрасте людей, в то время как df2 содержит информацию о поле людей. Не все пользователи находятся в df1 ни в df2

 df1
     Name   Age 
0     Tom    34
1     Sara   18
2     Eva    44
3     Jack   27
4     Laura  30

df2
     Name      Sex 
0     Tom       M
1     Paul      M
2     Eva       F
3     Jack      M
4     Michelle  F
  

Я хочу иметь информацию о поле людей в df1 и настройках NaN , если у меня нет этой информации в df2 . Я пытался это сделать, df1 = pd.merge(df1, df2, on = 'Name', how = 'outer') но я сохраняю информацию некоторых людей в df2 , которая мне не нужна.

 df1
     Name   Age     Sex
0     Tom    34      M
1     Sara   18     NaN
2     Eva    44      F
3     Jack   27      M
4     Laura  30     NaN
  

Ответ №1:

Sample :

 df1 = pd.DataFrame({'Name': ['Tom', 'Sara', 'Eva', 'Jack', 'Laura'], 
                    'Age': [34, 18, 44, 27, 30]})

#print (df1)
df3 = df1.copy()

df2 = pd.DataFrame({'Name': ['Tom', 'Paul', 'Eva', 'Jack', 'Michelle'], 
                    'Sex': ['M', 'M', 'F', 'M', 'F']})
#print (df2)
  

Используйте map by Series , созданный set_index :

 df1['Sex'] = df1['Name'].map(df2.set_index('Name')['Sex'])
print (df1)
    Name  Age  Sex
0    Tom   34    M
1   Sara   18  NaN
2    Eva   44    F
3   Jack   27    M
4  Laura   30  NaN
  

Альтернативное решение с merge соединением по левому краю:

 df = df3.merge(df2[['Name','Sex']], on='Name', how='left')
print (df)
    Name  Age  Sex
0    Tom   34    M
1   Sara   18  NaN
2    Eva   44    F
3   Jack   27    M
4  Laura   30  NaN
  

Если требуется сопоставление по нескольким столбцам (например, Year и Code ), необходимо merge соединить слева:

 df1 = pd.DataFrame({'Name': ['Tom', 'Sara', 'Eva', 'Jack', 'Laura'], 
                    'Year':[2000,2003,2003,2004,2007],
                    'Code':[1,2,3,4,4],
                    'Age': [34, 18, 44, 27, 30]})

print (df1)
    Name  Year  Code  Age
0    Tom  2000     1   34
1   Sara  2003     2   18
2    Eva  2003     3   44
3   Jack  2004     4   27
4  Laura  2007     4   30

df2 = pd.DataFrame({'Name': ['Tom', 'Paul', 'Eva', 'Jack', 'Michelle'], 
                    'Sex': ['M', 'M', 'F', 'M', 'F'],
                    'Year':[2001,2003,2003,2004,2007],
                    'Code':[1,2,3,5,3],
                    'Val':[21,34,23,44,67]})
print (df2)
       Name Sex  Year  Code  Val
0       Tom   M  2001     1   21
1      Paul   M  2003     2   34
2       Eva   F  2003     3   23
3      Jack   M  2004     5   44
4  Michelle   F  2007     3   67
  
 #merge by all columns
df = df1.merge(df2, on=['Year','Code'], how='left')
print (df)
  Name_x  Year  Code  Age Name_y  Sex   Val
0    Tom  2000     1   34    NaN  NaN   NaN
1   Sara  2003     2   18   Paul    M  34.0
2    Eva  2003     3   44    Eva    F  23.0
3   Jack  2004     4   27    NaN  NaN   NaN
4  Laura  2007     4   30    NaN  NaN   NaN

#specified columns - columns for join (Year, Code) need always   appended columns (Val)
df = df1.merge(df2[['Year','Code', 'Val']], on=['Year','Code'], how='left')
print (df)
    Name  Year  Code  Age   Val
0    Tom  2000     1   34   NaN
1   Sara  2003     2   18  34.0
2    Eva  2003     3   44  23.0
3   Jack  2004     4   27   NaN
4  Laura  2007     4   30   NaN
  

Если выдается ошибка с map , это означает дублирование по столбцам join, здесь Name :

 df1 = pd.DataFrame({'Name': ['Tom', 'Sara', 'Eva', 'Jack', 'Laura'], 
                    'Age': [34, 18, 44, 27, 30]})

print (df1)
    Name  Age
0    Tom   34
1   Sara   18
2    Eva   44
3   Jack   27
4  Laura   30

df3, df4 = df1.copy(), df1.copy()

df2 = pd.DataFrame({'Name': ['Tom', 'Tom', 'Eva', 'Jack', 'Michelle'], 
                    'Val': [1,2,3,4,5]})
print (df2)
       Name  Val
0       Tom    1 <-duplicated name Tom
1       Tom    2 <-duplicated name Tom
2       Eva    3
3      Jack    4
4  Michelle    5

s = df2.set_index('Name')['Val']
df1['New'] = df1['Name'].map(s)
print (df1)
  

InvalidIndexError: Переиндексация допустима только с объектами индекса с уникальным значением

Решения удаляют дубликаты с помощью DataFrame.drop_duplicates или используют map by dict для последнего совпадения:

 #default keep first value
s = df2.drop_duplicates('Name').set_index('Name')['Val']
print (s)
Name
Tom         1
Eva         3
Jack        4
Michelle    5
Name: Val, dtype: int64

df1['New'] = df1['Name'].map(s)
print (df1)
    Name  Age  New
0    Tom   34  1.0
1   Sara   18  NaN
2    Eva   44  3.0
3   Jack   27  4.0
4  Laura   30  NaN
  
 #add parameter for keep last value 
s = df2.drop_duplicates('Name', keep='last').set_index('Name')['Val']
print (s)
Name
Tom         2
Eva         3
Jack        4
Michelle    5
Name: Val, dtype: int64

df3['New'] = df3['Name'].map(s)
print (df3)
    Name  Age  New
0    Tom   34  2.0
1   Sara   18  NaN
2    Eva   44  3.0
3   Jack   27  4.0
4  Laura   30  NaN
  
 #map by dictionary
d = dict(zip(df2['Name'], df2['Val']))
print (d)
{'Tom': 2, 'Eva': 3, 'Jack': 4, 'Michelle': 5}

df4['New'] = df4['Name'].map(d)
print (df4)
    Name  Age  New
0    Tom   34  2.0
1   Sara   18  NaN
2    Eva   44  3.0
3   Jack   27  4.0
4  Laura   30  NaN
  

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

1. здравствуйте, как использовать df1['Sex'] = df1['Name'].map(df2.set_index('Name')['Sex']) , когда во втором фрейме данных разное количество строк? я использую его в своем наборе данных и получаю результаты только для первой строки, спасибо

2. @sygneto — Это должно работать, значения совпадают? Что возвращает print (df1['Sex'].unique()) vs print (df2['Sex'].unique()) ?

3. у меня есть все уникальные значения, но в моем случае этот столбец df1['sex'] уже существует и имеет в каждой строке значение = 0, как вы думаете, есть способ его заменить? или, может быть, удалить этот столбец перед отображением?

4. @sygneto — Мне нелегко увидеть проблему, потому что я не могу видеть ваши данные. 🙁

5. я думаю, причина в том, что, поскольку у меня уже есть столбец [‘sex’] в обоих фреймах данных, как я могу заменить его или добавить?

Ответ №2:

Вы также можете использовать join метод:

 df1.set_index("Name").join(df2.set_index("Name"), how="left")
  

редактировать: добавлено set_index("Name")

Ответ №3:

О переиндексации пока не упоминалось, но она очень быстрая и при желании может автоматически заполнять недостающие значения.


DataFrame.reindex

Используйте общий ключ ( Name ) в качестве индекса фрейма данных сопоставления ( df2 ):

  • Если df2 индекс уже есть Name , просто reindex напрямую:

     df2['Sex'].reindex(df1['Name'])
      
  • В противном случае set_index заранее:

     df2.set_index('Name')['Sex'].reindex(df1['Name'])
      

Обратите внимание, что при назначении в существующий фрейм данных переиндексированный индекс будет смещен, поэтому присваивайте только значения массива:

 df1['Sex'] = df2.set_index('Name')['Sex'].reindex(df1['Name']).array

#     Name  Age  Sex
# 0    Tom   34    M
# 1   Sara   18  NaN
# 2    Eva   44    F
# 3   Jack   27    M
# 4  Laura   30  NaN
  

Также я заметил распространенное предположение, что переиндексация выполняется медленно, но на самом деле это быстро (est):

тайминги переиндексации


Для заполнения недостающих значений

reindex поддерживает автоматическое заполнение пропущенных значений:

  • fill_value : статическая замена
  • method : алгоритмическая замена ( ffill , bfill или nearest ) с заданным монотонным индексом

Например, для заполнения пустых Sex значений с помощью Предпочитаю не говорить (PNS):

 df2.set_index('Name')['Sex'].reindex(df1['Name'], fill_value='PNS')

#     Name  Age  Sex
# 0    Tom   34    M
# 1   Sara   18  PNS
# 2    Eva   44    F
# 3   Jack   27    M
# 4  Laura   30  PNS
  

Переиндексация с помощью fill_value выполняется быстрее, чем объединение в цепочку fillna :

переиндексировать с использованием времени заполнения


Для обработки дубликатов

Сопоставляющий фрейм данных ( df2 ) не может иметь повторяющихся ключей, поэтому, drop_duplicates если применимо:

 df2.drop_duplicates('Name').set_index('Name')['Sex'].reindex(df1['Name'])
  

Данные синхронизации:

 '''
Note: This is python code in a js snippet, so "run code snippet" will not work.
The snippet is just to avoid cluttering the main post with supplemental code.
'''

df1 = pd.DataFrame({'Name': np.arange(n), 'Age': np.random.randint(100, size=n)}).sample(frac=1).reset_index(drop=True)
df2 = pd.DataFrame({'Name': np.arange(n)   int(n * 0.5), 'Sex': np.random.choice(list('MF'), size=n)}).sample(frac=1).reset_index(drop=True)

def reindex_(df1, df2):
    df1['Sex'] = df2.set_index('Name')['Sex'].reindex(df1['Name']).array
    return df1

def map_(df1, df2):
    df1['Sex'] = df1['Name'].map(df2.set_index('Name')['Sex'])
    return df1

def dict_(df1, df2):
    df1['Sex'] = df1['Name'].map(dict(zip(df2['Name'], df2['Sex'])))
    return df1

def merge_(df1, df2):
    return df1.merge(df2[['Name', 'Sex']], left_on='Name', right_on='Name', how='left')

def join_(df1, df2):
    return df1.set_index('Name').join(df2.set_index('Name'), how='left').reset_index()

reindex_fill_value_ = lambda df1, df2: df2.set_index('Name')['Sex'].reindex(df1['Name'], fill_value='PNTS')
reindex_fillna_ = lambda df1, df2: df2.set_index('Name')['Sex'].reindex(df1['Name']).fillna('PNTS')
map_fillna_ = lambda df1, df2: df1['Name'].map(df2.set_index('Name')['Sex']).fillna('PNTS')  

Ответ №4:

Простое дополнение к ответу @jezrael для создания словаря из фрейма данных.

Это может быть полезно..

Python:

 df1 = pd.DataFrame({'Name': ['Tom', 'Sara', 'Eva', 'Jack', 'Laura'],
                    'Age': [34, 18, 44, 27, 30]})


df2 = pd.DataFrame({'Name': ['Tom', 'Paul', 'Eva', 'Paul', 'Jack', 'Michelle', 'Tom'],
                    'Something': ['M', 'M', 'F', 'M', 'A', 'F', 'B']})


df1_dict = pd.Series(df1.Age.values, index=df1.Name).to_dict()

df2['Age'] = df2['Name'].map(df1_dict)

print(df2)
  

Вывод:

       Name Something   Age
0       Tom         M  34.0
1      Paul         M   NaN
2       Eva         F  44.0
3      Paul         M   NaN
4      Jack         A  27.0
5  Michelle         F   NaN
6       Tom         B  34.0