Должны ли мы использовать для понимания цикла или списка при создании DataFrames из нескольких источников данных? - PullRequest
0 голосов
/ 05 ноября 2018

Этот вопрос связан с ответом @jpp: s в Объединение файлов с похожим соглашением имен с фреймом данных и решением пометить более ранний поток ( Поместить csv-файлы в отдельные фреймы данных в зависимости от имя файла ) как дубликат, потому что три ответа в этой теме либо не работали (2/3), либо плохо (1/3).

Независимо от ответов, которые не работали, один ответ (мой ответ), как говорили, был низкого качества, потому что "использование concat в цикле for явно не рекомендуется документами" .


Критикуемый метод :

dataframes = {}
for filename in filenames:
    _df = pd.read_csv(filename)
    key = filename[:3]
    try:
       dataframes[key] = pd.concat([dataframes[key], _df], ignore_index=True)
    except KeyError:
       dataframes[key] = _df

Принятый метод (dd - это словарь, в котором каждое значение представляет собой список имен файлов, а каждый ключ - первые три символа каждого имени файла):

dict_of_dfs
for k, v in dd.items():
    dict_of_dfs[k] = pd.concat([pd.read_csv(fn) for fn in v], ignore_index=True)

Теперь я согласен, что понимание списка в вызове concat (принятый метод) на эффективнее , чем цикл for, где concat вызывается для каждого DataFrame.

Но значит ли это, что мы должны всегда создавать DataFrames из нескольких источников данных, используя понимание списка в вызове concat (или append), и использование цикла for так бедно что на самом деле неправильно ? А как насчет читабельности? Я лично (конечно) думаю, что мой критикуемый метод более читабелен.


Если мы прочитаем pandas документы в DataFrame.append, мы узнаем, что ни для циклов, ни для составления списков не "рекомендуемые методы для генерации DataFrames":

Ниже, , хотя и не рекомендуемые методы для генерации DataFrames , показаны два способа генерации DataFrame из нескольких источников данных.

Менее эффективно:

>>> df = pd.DataFrame(columns=['A'])
>>> for i in range(5):
...     df = df.append({'A': i}, ignore_index=True)
>>> df

   A
0  0
1  1
2  2
3  3
4  4

Более эффективно:

>>> pd.concat([pd.DataFrame([i], columns=['A']) for i in range(5)],
...           ignore_index=True)

   A
0  0
1  1
2  2
3  3
4  4

Итак. Мои вопросы следующие:

  1. Зацикливается и использует concat на нескольких источниках данных для создания одного или нескольких экземпляров DataFrame, поэтому плохо , что неправильно ?

  2. Должны ли мы всегда использовать понимание списка в таком случае?

  3. Документы, похоже, не рекомендуют использовать ни списки списков, ни цикл for, так каков рекомендуемый способ создания DataFrame из нескольких источников данных?


Я очень ценю ваши ответы @piRSquared и @jpp. Я до сих пор не убежден в том, что категорическое отклонение циклов concat в for как плохо до такой степени, что неправильно , в то время как списочные значения правильны и принят .

Учитывая следующие данные испытаний:

df = pd.DataFrame({'A': np.arange(0, 25000), 'B': np.arange(0, 25000)})

for i in range(0, 50):
    df.to_csv('{}.csv'.format(i))

Методы:

def conc_inside_loop(filenames):
    df = None
    for filename in filenames:

        if df is None:
           df = pd.read_csv(filename)
           continue

        df = pd.concat([df, pd.read_csv(filename)], ignore_index=True)

    return df

def conc_list_comprehension(filenames):
    return pd.concat([pd.read_csv(filename) for filename in filenames], ignore_index=True)

Times:

>> %timeit -n 10 conc_inside_loop(glob.glob('*.csv'))
460 ms ± 15.4 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)


>> %timeit -n 10 conc_list_comprehension(glob.glob('*.csv'))
363 ms ± 32.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

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

Как сказал @piRSquared, последний вопрос слишком широкий. Но третий способ - использовать concat вне цикла for:

def conc_outside_loop(filenames):

    df_list = []
    for filename in filenames:
        df_list.append(pd.read_csv(filename))

    return pd.concat(df_list, ignore_index=True)

>> %timeit -n 10 conc_outside_loop(glob.glob('*.csv'))
344 ms ± 23.4 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Ответы [ 2 ]

0 голосов
/ 05 ноября 2018

Давайте проясним, pd.DataFrame.append / pd.concat в цикле не рекомендуется . @ ответ piRSquared объясняет, почему в этом документе также есть явные указания.

Причина заключается в том, как структурированы массивы NumPy. Вы не можете добавлять / объединять их эффективно; эти операции требуют создания копий данных. Операции занимают много памяти и, как правило, неэффективны.

Таким образом, «принятый» метод является меньшим из двух зол, поскольку вы выполняете относительно небольшое количество вызовов pd.concat по сравнению с одним для каждого имени файла в списке ввода через " раскритиковал "метод.

Объединить только один раз

Вы можете объединить все своих фреймов данных и затем выполнить операцию GroupBy:

df = pd.concat([pd.read_csv(fn).assign(file=fn.split('_')[0]) for fn in v],
               ignore_index=True)

dict_of_dfs = dict(tuple(df.groupby('file')))

Ключ, простите за каламбур, заключается в уменьшении количества операций конкатенации.

0 голосов
/ 05 ноября 2018
  1. Зацикливается и использует concat на нескольких источниках данных для создания одного или нескольких экземпляров DataFrame, поэтому плохо , что неправильно ?

Да! Панды великолепны. Но вы должны любой ценой избегать ненужного производства предметов Pandas. Создание объектов Pandas может быть дорогим, DataFrames больше, чем Series, но это, вероятно, True для всего Python. Для «критикуемого» метода: внутри цикла вы создаете объект Pandas, который будет перезаписан на следующей итерации цикла. Вместо этого вам следует подумать о том, как собрать свои данные, чтобы получить объект Pandas в конце сбора.

  1. Должны ли мы всегда использовать понимание списка в таком случае?

Нет! Как я уже говорил выше, думайте об этом как о сборе данных при подготовке к строительству объекта Pandas. Понимание - это только один такой способ собраться.

  1. Документы, похоже, не рекомендуют использовать ни списки, ни цикл for, так каков рекомендуемый способ создания DataFrame (s) из нескольких источников данных?

Это слишком широко. Дело может быть сделано для многих подходов. Только не используйте concat или append в цикле. Я бы назвал это неправильно почти каждый раз.

И под словом "каждый раз" я на самом деле не подразумеваю "каждый раз". Что я DO имею в виду, так это то, что вы никогда не должны создавать фрейм данных в какой-то момент до цикла, затем зацикливаться и на каждой итерации сталкиваться с проблемой добавления чего-либо в предыдущий инициализированный фрейм данных. Каждая итерация становится очень дорогой. В случае ответа «Принято»: он присваивает фрейм данных ключу словаря и затем остается один. Не повторяется.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...