Подтвердить что ты не робот

Возврат нескольких столбцов из приложения pandas

У меня есть pandas DataFrame, df_test. Он содержит столбец "размер", который представляет размер в байтах. Я вычислил KB, MB и GB, используя следующий код:

df_test = pd.DataFrame([
    {'dir': '/Users/uname1', 'size': 994933},
    {'dir': '/Users/uname2', 'size': 109338711},
])

df_test['size_kb'] = df_test['size'].astype(int).apply(lambda x: locale.format("%.1f", x / 1024.0, grouping=True) + ' KB')
df_test['size_mb'] = df_test['size'].astype(int).apply(lambda x: locale.format("%.1f", x / 1024.0 ** 2, grouping=True) + ' MB')
df_test['size_gb'] = df_test['size'].astype(int).apply(lambda x: locale.format("%.1f", x / 1024.0 ** 3, grouping=True) + ' GB')

df_test


             dir       size       size_kb   size_mb size_gb
0  /Users/uname1     994933      971.6 KB    0.9 MB  0.0 GB
1  /Users/uname2  109338711  106,776.1 KB  104.3 MB  0.1 GB

[2 rows x 5 columns]

Я запустил это более 120 000 строк и времени, затрачивая около 2,97 секунды на столбец * 3 = ~ 9 секунд в зависимости от% timeit.

Есть ли способ сделать это быстрее? Например, могу ли я вместо того, чтобы возвращать один столбец за один раз из приложения и запускать его 3 раза, могу ли я вернуть все три столбца за один проход, чтобы вставить обратно в исходный фрейм?

Другие вопросы, которые я нашел, все хотят принимать несколько значений и возвращать одно значение. Я хочу взять одно значение и вернуть несколько столбцов.

4b9b3361

Ответ 1

Это старый вопрос, но для полноты вы можете вернуть Серию из прикладной функции, которая содержит новые данные, предотвращая необходимость повторять три раза. Передача axis=1 в функцию apply применяет функцию sizes к каждой строке кадра данных, возвращая серию, чтобы добавить в новый фрейм данных. Эта серия, s, содержит новые значения, а также исходные данные.

def sizes(s):
    s['size_kb'] = locale.format("%.1f", s['size'] / 1024.0, grouping=True) + ' KB'
    s['size_mb'] = locale.format("%.1f", s['size'] / 1024.0 ** 2, grouping=True) + ' MB'
    s['size_gb'] = locale.format("%.1f", s['size'] / 1024.0 ** 3, grouping=True) + ' GB'
    return s

df_test = df_test.append(rows_list)
df_test = df_test.apply(sizes, axis=1)

Ответ 2

Используйте apply и zip будет 3 раза быстрее, чем серия.

def sizes(s):    
    return locale.format("%.1f", s / 1024.0, grouping=True) + ' KB', \
        locale.format("%.1f", s / 1024.0 ** 2, grouping=True) + ' MB', \
        locale.format("%.1f", s / 1024.0 ** 3, grouping=True) + ' GB'
df_test['size_kb'],  df_test['size_mb'], df_test['size_gb'] = zip(*df_test['size'].apply(sizes))

Результат теста:

Separate df.apply(): 

    100 loops, best of 3: 1.43 ms per loop

Return Series: 

    100 loops, best of 3: 2.61 ms per loop

Return tuple:

    1000 loops, best of 3: 819 µs per loop

Ответ 3

Некоторые из текущих ответов работают нормально, но я хочу предложить другой, возможно, более "панифицированный" вариант. Это работает для меня с текущими пандами 0.23 (не уверен, будет ли это работать в предыдущих версиях):

import pandas as pd

df_test = pd.DataFrame([
  {'dir': '/Users/uname1', 'size': 994933},
  {'dir': '/Users/uname2', 'size': 109338711},
])

def sizes(s):
  a = locale.format("%.1f", s['size'] / 1024.0, grouping=True) + ' KB'
  b = locale.format("%.1f", s['size'] / 1024.0 ** 2, grouping=True) + ' MB'
  c = locale.format("%.1f", s['size'] / 1024.0 ** 3, grouping=True) + ' GB'
  return a, b, c

df_test[['size_kb', 'size_mb', 'size_gb']] = df_test.apply(sizes, axis=1, result_type="expand")

Обратите внимание, что хитрость заключается в result_type параметра result_type для apply, который расширит свой результат в DataFrame который можно напрямую назначить новым/старым столбцам.

Ответ 4

Просто еще один читаемый способ. Этот код добавит три новых столбца и их значения, возвращая ряды без параметров использования в функции apply.

def sizes(s):

    val_kb = locale.format("%.1f", s['size'] / 1024.0, grouping=True) + ' KB'
    val_mb = locale.format("%.1f", s['size'] / 1024.0 ** 2, grouping=True) + ' MB'
    val_gb = locale.format("%.1f", s['size'] / 1024.0 ** 3, grouping=True) + ' GB'
    return pd.Series([val_kb,val_mb,val_gb],index=['size_kb','size_mb','size_gb'])

df[['size_kb','size_mb','size_gb']] = df.apply(lambda x: sizes(x) , axis=1)

Общий пример с: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.apply.html

df.apply(lambda x: pd.Series([1, 2], index=['foo', 'bar']), axis=1)

#foo  bar
#0    1    2
#1    1    2
#2    1    2

Ответ 5

Как правило, для возврата нескольких значений это то, что я делаю

def gimmeMultiple(group):
    x1 = 1
    x2 = 2
    return array([[1, 2]])
def gimmeMultipleDf(group):
    x1 = 1
    x2 = 2
    return pd.DataFrame(array([[1,2]]), columns=['x1', 'x2'])
df['size'].astype(int).apply(gimmeMultiple)
df['size'].astype(int).apply(gimmeMultipleDf)

Возвращение данных-кадров окончательно имеет свои привилегии, но иногда не требуется. Вы можете посмотреть, что возвращает apply() и немного поиграть с функциями;)

Ответ 6

Действительно классные ответы! Спасибо Джесси и Жаумебонет! Просто некоторые замечания в отношении:

  • zip(* ...
  • ... result_type="expand")

Хотя расширение является более элегантным (pandifyed), zip по крайней мере в 2 раза быстрее. На этом простом примере ниже я получил в 4 раза быстрее.

import pandas as pd

dat = [ [i, 10*i] for i in range(1000)]

df = pd.DataFrame(dat, columns = ["a","b"])

def add_and_sub(row):
    add = row["a"] + row["b"]
    sub = row["a"] - row["b"]
    return add, sub

df[["add", "sub"]] = df.apply(add_and_sub, axis=1, result_type="expand")
# versus
df["add"], df["sub"] = zip(*df.apply(add_and_sub, axis=1))

Ответ 7

Просто используйте result_type="expand"

df = pd.DataFrame(np.random.randint(0,10,(10,2)), columns=["random", "a"])
df[["sq_a","cube_a"]] = df.apply(lambda x: [x.a**2, x.a**3], axis=1, result_type="expand")