Я пытаюсь написать функцию, которая разбивает строку pandas на несколько строк и обновляет некоторые значения в определенном столбце.
Проблема будет выглядеть так;
Id Values
0 A 2000
1 B 600
и ожидаемые результаты после разделения только тех Идентификаторы с Стоимость, превышающим 800, на меньшие количества. Это было бы;
Id Values
0 A 800
1 A 800
2 A 400
3 B 600
Логика разделения значений не так актуальна. Например;
2000 = 800 + 800 + 400
2000 = 700 + 700 + 600
любые идеи о том, как решить эту проблему?
исходный фрейм данных имеет одиночные записи идентификаторов (не повторяющиеся значения). Я не вижу, как groupby.apply может мне помочь в этом случае :-/
Это быстрое решение должно дать требуемые результаты, код еще нуждается в оптимизации
max_val = 800
def split_x (x):
lst=[]
while x > max_val:
x=x-max_val
lst.append(max_val)
if x != 0:
lst.append(x)
return lst
data = {'id':['A', 'B'],
'value':[2000,600]}
df = pd.DataFrame(data)
df_new=pd.DataFrame({'id':[],'value':[]})
for i in range(len(df)):
d=df.loc[i]['value']
id=df.loc[i]['id']
s=split_x(d)
for i in range(len(s)):
print (i,s[i])
df_new=df_new.append({'id':id,'value':s[i]},ignore_index=True)
>>> df_new
id value
0 A 800.0
1 A 800.0
2 A 400.0
3 B 600.0
d=df.loc[i]['value']
обязательно должно быть d=df.loc[i, 'value']
?
@Dan оба результата одинаковы
да, но один из них - канонические панды, другой - просто следствие того, что ваш первый фрагмент также возвращает фрейм данных. Используйте запятую.
Определите функцию, которая будет применяться к каждой строке:
def fn2(val, maxVal):
tbl = []
v1 = val // maxVal
v2 = val % maxVal
if v1:
tbl.extend([maxVal] * v1)
if v2:
tbl.append(v2)
return pd.Series(tbl)
В приведенной выше функции максвал является максимальным значением стоимость, которое должно быть установлено в выходной ряд.
И фактическая обработка может быть выполнена в одном (хотя и связанном) инструкция:
df.set_index('Id').Values.apply(fn2, maxVal=800).stack()\
.rename('Values').astype(int)\
.reset_index(level=1, drop=True).reset_index()
Обратите внимание, что перед куча некоторые значения равны NaN, поэтому тип был изменен до плавать. Чтобы изменить его обратно на инт, я добавил тип (целое число).
Для ваших выборочных данных результат:
Id Values
0 A 800
1 A 800
2 A 400
3 B 600
Если у вас есть единственный другой столбец, вы можете "разделить" Значения на куски почти как вы предложили:
df.set_index(['Id', 'AnotherCol']).Values.apply(fn2, maxVal=800)\
.stack().rename('Values').astype(int)\
.reset_index(level=2, drop=True).reset_index()
Отличие от вашего предложения в том, что уровень индекса, который нужно сбросить, равен 2. (не 0).
Но если у вас есть более таких «дополнительных» столбцов, более естественным кажется:
Id
.Id
,Итак, код:
vals = df.set_index(['Id']).Values.apply(fn2, maxVal=800)\
.stack().rename('Values').astype(int)\
.reset_index(level=1, drop=True)
pd.merge(df.drop(columns=['Values']), vals,
left_on='Id', right_index=True)
Если вы недовольны повторяющимися значениями индекса, добавьте .reset_index(drop=True)
до последней инструкции.
Спасибо за это отличное решение. В моем исходном df
больше столбцов anotherCol
, поэтому я могу предположить, что продолжение инструкции по цепочке будет таким: df.set_index(['Id','anotherCol']).Values.apply(fn2,maxVal=800).stack().rename('Values').astype(int).reset_index(level=0, drop=True).reset_index().drop(['level_1'], axis=1)
@Valdi_Bo, вы бы сделали это по-другому?
просто воспроизвел вашу альтернативу, это было очень полезно увидеть. Я узнал одну или две вещи из этого... Спасибо. На вашем последнем шаге мне пришлось преобразовать series
в df
перед слиянием: pd.merge(df.drop(columns=['Values']), vals.to_frame(), left_on='Id', right_index=True)
Вы пробовали групповое применение?