Панды: Как заменить часть значений столбца на те же значения в определенной серии? [дубликат]

Вопрос или проблема

Я пишу скрипт на Pandas для выполнения манипуляций с данными в Excel-файле. Сначала я загружаю два листа в DataFrame. Один – это оригинальные данные df, второй – лист с деталями замен, которые необходимо сделать в оригинальных данных replace.

Скрипт должен выполнять две задачи для каждой строки в df.

  1. Заменить каждое вхождение 'Name' в df на 'NameReplace' (работает)

  2. Для тех же строк в df заменить срез столбцов (указанный в списке) значениями из того же среза столбцов в replace

Воспроизводимый минимальный пример моей текущей реализации:

import pandas

df = pandas.DataFrame([["John", None, None],["Phil", None, None],["John", None, None],["Bob", None, None]], columns=["Name", "Age", "Height"])
replace = pandas.DataFrame([["John", "Dom", 25, 175],["Phil", "Kevin", 56, 145],["Bob", "Michael", 33, 180]], columns=["Name", "NameReplace", "Age", "Height"])

detailsList = ["Age", "Height"]

for i, row in replace.iterrows():
    df.loc[df['Name'] == row['Name'], 'Name'] = row['NameReplace']
    df.loc[df['Name'] == row['NameReplace'], detailsList] = row[detailsList]

print(df)

Шаг 1) работает с этой реализацией, но столбцы detailsList в df не заполняются.
Текущий вывод:

      Name  Age Height
0      Dom  NaN    NaN
1    Kevin  NaN    NaN
2      Dom  NaN    NaN
3  Michael  NaN    NaN

Желаемый вывод:

      Name  Age Height
0      Dom  25    175
1    Kevin  56    145
2      Dom  25    175
3  Michael  33    180

Я уже некоторое время пытаюсь решить эту проблему и не могу продвинуться. Я также не совсем понимаю, почему это не работает, поэтому любая информация об этом будет очень ценна!

Примечание: Использование detailsList для указания среза столбцов обязательно, так как в реальном решении я работаю только с конкретным срезом полного DataFrame, в отличие от примера, который я привел.

Проблема заключается в том, как pandas пытается назначить серию всему DataFrame. В любом случае, вот простое решение, которое обеспечивает необходимое поведение, воспользовавшись тем, что pandas делает правильные действия при присвоении с помощью массива numpy, а не с помощью серии.

for i, row in replace.iterrows():
    df.loc[df['Name'] == row['Name'], 'Name'] = row['NameReplace']
    df.loc[df['Name'] == row['NameReplace'], detailsList] = row[detailsList].values

Другие оптимизации:

  • Обратите внимание, что вы можете повторно использовать маску df['Name'] == row['Name']. В частности, вы сэкономите немного усилий с:
for i, row in replace.iterrows():
    mask = df['Name'] == row['Name']
    df.loc[mask, 'Name'] = row['NameReplace']
    df.loc[mask, detailsList] = row[detailsList].values
  • Вы можете избежать использования iterrows, если используете объединение
df = (df[['Name']].merge(replace, on = 'Name')
                  .drop(columns="Name")
                  .rename(columns={'NameReplace':'Name'}))

Однако при этом подходе строки могут оказаться переупорядоченными.

Ответ или решение

Чтобы заменить часть значений в столбцах одного DataFrame на значения из другого DataFrame по определённым условиям в Pandas, можно использовать несколько подходов. Ниже приведен подробный ответ на ваш запрос, включающий все необходимые шаги.

Шаги выполнения задачи

  1. Импорт библиотеки и создание DataFrame.
    Мы начнём с импорта нужной библиотеки и создания двух DataFrame: один содержит оригинальные данные (df), другой — данные о замене (replace).

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

Пример кода

import pandas as pd

# Создание оригинального DataFrame
df = pd.DataFrame([
    ["John", None, None],
    ["Phil", None, None],
    ["John", None, None],
    ["Bob", None, None]
], columns=["Name", "Age", "Height"])

# Создание DataFrame с заменами
replace = pd.DataFrame([
    ["John", "Dom", 25, 175],
    ["Phil", "Kevin", 56, 145],
    ["Bob", "Michael", 33, 180]
], columns=["Name", "NameReplace", "Age", "Height"])

# Список столбцов для замены
detailsList = ["Age", "Height"]

# Проход по рядам в DataFrame с заменами
for i, row in replace.iterrows():
    # Создание маски для текущего имени
    mask = df['Name'] == row['Name']

    # Замена имени
    df.loc[mask, 'Name'] = row['NameReplace']

    # Замена значений в указанных столбцах
    df.loc[mask, detailsList] = row[detailsList].values

# Печать итогового DataFrame
print(df)

Результат выполнения кода

После выполнения данного кода, вывод будет следующим:

      Name   Age Height
0      Dom  25.0    175
1   Kevin  56.0    145
2      Dom  25.0    175
3  Michael  33.0    180

Объяснение

  1. Создание маски. Мы создаем маску mask, которая определяет, какие строки в df соответствуют текущему имени из replace.

  2. Замена имени. С помощью df.loc[mask, 'Name'] мы заменяем имя в тех строках, где оно совпадает с текущим именем из replace.

  3. Замена значений в столбцах. Для замены значений в других столбцах (в нашем случае это Age и Height), используем df.loc[mask, detailsList] = row[detailsList].values. Это позволяет избежать проблем с несовпадением индексов.

Оптимизация

Если необходимо, можно улучшить производительность за счёт использования метода merge, что избавит вас от необходимости проходить по каждой строке. Однако стоит учитывать, что в этом случае порядок строк может измениться:

df = (df[['Name']]
      .merge(replace, on='Name', how='left')
      .drop(columns='Name')
      .rename(columns={'NameReplace': 'Name'}))

Заключение

Таким образом, вы можете эффективно выполнять замену строк и значений в DataFrame с использованием методов Pandas. Важно помнить о механизмах работы с индексами и тем, как Pandas управляет преобразованиями данных для минимизации возможных ошибок.

Оцените материал
Добавить комментарий

Капча загружается...