Разбить (взорвать) диапазон в dataframe на несколько строк
этот вопрос похож на разделить (взорвать) запись строки фрейма данных pandas на отдельные строки, но включает в себя вопрос о добавлении диапазонов.
у меня есть фрейм данных:
+------+---------+----------------+
| Name | Options | Email |
+------+---------+----------------+
| Bob | 1,2,4-6 | bob@email.com |
+------+---------+----------------+
| John | NaN | john@email.com |
+------+---------+----------------+
| Mary | 1,2 | mary@email.com |
+------+---------+----------------+
| Jane | 1,3-5 | jane@email.com |
+------+---------+----------------+
и я хотел бы Options
столбец, разделяемый запятой, а также строки, добавленные для диапазона.
+------+---------+----------------+
| Name | Options | Email |
+------+---------+----------------+
| Bob | 1 | bob@email.com |
+------+---------+----------------+
| Bob | 2 | bob@email.com |
+------+---------+----------------+
| Bob | 4 | bob@email.com |
+------+---------+----------------+
| Bob | 5 | bob@email.com |
+------+---------+----------------+
| Bob | 6 | bob@email.com |
+------+---------+----------------+
| John | NaN | john@email.com |
+------+---------+----------------+
| Mary | 1 | mary@email.com |
+------+---------+----------------+
| Mary | 2 | mary@email.com |
+------+---------+----------------+
| Jane | 1 | jane@email.com |
+------+---------+----------------+
| Jane | 3 | jane@email.com |
+------+---------+----------------+
| Jane | 4 | jane@email.com |
+------+---------+----------------+
| Jane | 5 | jane@email.com |
+------+---------+----------------+
как я могу выйти за рамки использования concat
и split
как ссылка, поэтому статья говорит, чтобы выполнить это? Мне нужен способ добавить диапазон.
в этой статье используется следующий код для разделения разделенных запятыми значений (1,2,3
):
In [7]: a
Out[7]:
var1 var2
0 a,b,c 1
1 d,e,f 2
In [55]: pd.concat([Series(row['var2'], row['var1'].split(','))
for _, row in a.iterrows()]).reset_index()
Out[55]:
index 0
0 a 1
1 b 1
2 c 1
3 d 2
4 e 2
5 f 2
заранее спасибо за ваши предложения!
обновление 2/14 образцы данных были обновлены в соответствии с моим текущим случаем.
4 ответов
мне нравится использовать np.r_
и slice
Я знаю, это выглядит как беспорядок, но красота в глазах смотрящего.
def parse(o):
mm = lambda i: slice(min(i), max(i) + 1)
return np.r_.__getitem__(tuple(
mm(list(map(int, s.strip().split('-')))) for s in o.split(',')
))
r = df.Options.apply(parse)
new = np.concatenate(r.values)
lens = r.str.len()
df.loc[df.index.repeat(lens)].assign(Options=new)
Name Options Email
0 Bob 1 bob@email.com
0 Bob 2 bob@email.com
0 Bob 4 bob@email.com
0 Bob 5 bob@email.com
0 Bob 6 bob@email.com
2 Mary 1 mary@email.com
2 Mary 2 mary@email.com
3 Jane 1 jane@email.com
3 Jane 3 jane@email.com
3 Jane 4 jane@email.com
3 Jane 5 jane@email.com
объяснение
-
np.r_
принимает различные слайсеры и индексаторы и возвращает массив комбинации.np.r_[1, 4:7] array([1, 4, 5, 6])
или
np.r_[slice(1, 2), slice(4, 7)] array([1, 4, 5, 6])
но если мне нужно передать произвольную кучу из них, мне нужно передать
tuple
tonp.r_
s__getitem__
метод.np.r_.__getitem__((slice(1, 2), slice(4, 7), slice(10, 14))) array([ 1, 4, 5, 6, 10, 11, 12, 13])
поэтому я повторяю, анализирую, делаю срезы и передаю
np.r_.__getitem__
используйте комбинацию
loc
,pd.Index.repeat
иpd.Series.str.len
после применения моего классного парсера- использовать
pd.DataFrame.assign
перезаписать существующий столбец
__Примечание__
Если у вас плохие символы Options
столбец, я бы попытался фильтровать так.
df = df.dropna(subset=['Options']).astype(dict(Options=str)) \
.replace(dict(Options={'[^0-9,\-]': ''}), regex=True) \
.query('Options != ""')
Если я понимаю, что вам нужно
def yourfunc(s):
ranges = (x.split("-") for x in s.split(","))
return [i for r in ranges for i in range(int(r[0]), int(r[-1]) + 1)]
df.Options=df.Options.apply(yourfunc)
df
Out[114]:
Name Options Email
0 Bob [1, 2, 4, 5, 6] bob@email.com
1 Jane [1, 3, 4, 5] jane@email.com
df.set_index(['Name','Email']).Options.apply(pd.Series).stack().reset_index().drop('level_2',1)
Out[116]:
Name Email 0
0 Bob bob@email.com 1.0
1 Bob bob@email.com 2.0
2 Bob bob@email.com 4.0
3 Bob bob@email.com 5.0
4 Bob bob@email.com 6.0
5 Jane jane@email.com 1.0
6 Jane jane@email.com 3.0
7 Jane jane@email.com 4.0
8 Jane jane@email.com 5.0
начните с пользовательской функции замены:
def replace(x):
i, j = map(int, x.groups())
return ','.join(map(str, range(i, j + 1)))
сохранить имена столбцов где-нибудь, мы будем использовать их позже:
c = df.columns
далее заменить элементы в df.Options
, затем разделите на запятую:
v = df.Options.str.replace('(\d+)-(\d+)', replace).str.split(',')
затем измените свои данные и, наконец, загрузите в новый фрейм данных:
df = pd.DataFrame(
df.drop('Options', 1).values.repeat(v.str.len(), axis=0)
)
df.insert(c.get_loc('Options'), len(c) - 1, np.concatenate(v))
df.columns = c
df
Name Options Email
0 Bob 1 bob@email.com
1 Bob 2 bob@email.com
2 Bob 4 bob@email.com
3 Bob 5 bob@email.com
4 Bob 6 bob@email.com
5 Jane 1 jane@email.com
6 Jane 3 jane@email.com
7 Jane 4 jane@email.com
8 Jane 5 jane@email.com
вот одно решение. Хотя это не очень красиво (минимальное использование pandas
), это довольно эффективно.
import itertools, pandas as pd, numpy as np; concat = itertools.chain.from_iterable
def ranger(mystr):
return list(concat([int(i)] if '-' not in i else \
list(range(int(i.split('-')[0]), int(i.split('-')[-1])+1)) \
for i in mystr.split(',')))
df = pd.DataFrame([['Bob', '1,2,4-6', 'bob@email.com'],
['Jane', '1,3-5', 'jane@email.com']],
columns=['Name', 'Options', 'Email'])
df['Options'] = df['Options'].map(ranger)
lens = list(map(len, df['Options']))
df_out = pd.DataFrame({'Name': np.repeat(df['Name'].values, lens),
'Email': np.repeat(df['Email'].values, lens),
'Option': np.hstack(df['Options'].values)})
# Email Name Option
# 0 bob@email.com Bob 1
# 1 bob@email.com Bob 2
# 2 bob@email.com Bob 4
# 3 bob@email.com Bob 5
# 4 bob@email.com Bob 6
# 5 jane@email.com Jane 1
# 6 jane@email.com Jane 3
# 7 jane@email.com Jane 4
# 8 jane@email.com Jane 5
бенчмаркинг из 4 решений ниже (только для интереса).
как правило,repeat
сорта превосходят. Кроме того, решения, которые создают новые фреймы данных с нуля (в отличие от apply
) сделать лучше. Опускаемся до numpy
дает лучшие результаты.
import itertools, pandas as pd, numpy as np; concat = itertools.chain.from_iterable
def ranger(mystr):
return list(concat([int(i)] if '-' not in i else \
list(range(int(i.split('-')[0]), int(i.split('-')[-1])+1)) \
for i in mystr.split(',')))
def replace(x):
i, j = map(int, x.groups())
return ','.join(map(str, range(i, j + 1)))
def yourfunc(s):
ranges = (x.split("-") for x in s.split(","))
return [i for r in ranges for i in range(int(r[0]), int(r[-1]) + 1)]
def parse(o):
mm = lambda i: slice(min(i), max(i) + 1)
return np.r_.__getitem__(tuple(mm(list(map(int, s.strip().split('-')))) for s in o.split(',')))
df = pd.DataFrame([['Bob', '1,2,4-6', 'bob@email.com'],
['Jane', '1,3-5', 'jane@email.com']],
columns=['Name', 'Options', 'Email'])
df = pd.concat([df]*1000, ignore_index=True)
def explode_jp(df):
df['Options'] = df['Options'].map(ranger)
lens = list(map(len, df['Options']))
df_out = pd.DataFrame({'Name': np.repeat(df['Name'].values, lens),
'Email': np.repeat(df['Email'].values, lens),
'Option': np.hstack(df['Options'].values)})
return df_out
def explode_cs(df):
c = df.columns
v = df.Options.str.replace('(\d+)-(\d+)', replace).str.split(',')
df_out = pd.DataFrame(df.drop('Options', 1).values.repeat(v.str.len(), axis=0))
df_out.insert(c.get_loc('Options'), len(c) - 1, np.concatenate(v))
df_out.columns = c
return df_out
def explode_wen(df):
df.Options=df.Options.apply(yourfunc)
df_out = df.set_index(['Name','Email']).Options.apply(pd.Series).stack().reset_index().drop('level_2',1)
return df_out
def explode_pir(df):
r = df.Options.apply(parse)
df_out = df.loc[df.index.repeat(r.str.len())].assign(Options=np.concatenate(r))
return df_out
%timeit explode_jp(df.copy()) # 32.7 ms ± 1.54 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit explode_cs(df.copy()) # 90.6 ms ± 2.07 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit explode_wen(df.copy()) # 675 ms ± 12.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit explode_pir(df.copy()) # 163 ms ± 1.97 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)