Ошибка KeyError при назначении новых столбцов в панде DataFrame - PullRequest
0 голосов
/ 29 ноября 2018

Я делаю некоторую обработку на естественном языке, и у меня есть MultiIndexed DataFrame, который выглядит примерно так (за исключением того, что на самом деле около 3000 строк):

                             Title                                              N-grams
Period  Date                                                                                                                     
2015-01 2015-01-01 22:00:10  SIRF: Simultaneous Image Registration and Fusi...  [@SENTBEGIN paper, paper propose, propose nove...    
        2015-01-02 16:54:13  Generic construction of scale-invariantly coar...  [@SENTBEGIN encode, encode temporal, temporal ...
        2015-01-04 00:07:00  Understanding Trajectory Behavior: A Motion Pa...  [@SENTBEGIN mining, mining underlie, underlie ...
        2015-01-04 09:07:45  Hostile Intent Identification by Movement Patt...  [@SENTBEGIN the, the recent, recent year, year...
        2015-01-04 14:35:58  A New Method for Signal and Image Analysis: Th...  [@SENTBEGIN brief, brief review, review provid...

Что я хочу сделать, это подсчитатьсколько раз каждый n-грамм появляется в каждом месяце (отсюда первый индекс, «Период»).Делать это довольно просто, если это отнимает много времени (и поскольку каждая ячейка в столбце «N-грамм» является списком, я не уверен, что можно было бы многое сделать для его ускорения).Я создаю новый DataFrame для хранения счетчиков, используя этот код:

# Create the frequencies DataFrame.
period_index = ngrams.index.unique(level = "Period")
freqs = DataFrame(index = period_index)

# Count the n-grams in each period.
for period in period_index:
    for ngrams_list in ngrams.loc[period, "N-grams"]:
        for ngram in ngrams_list:
            if not ngram in freqs.columns:
                freqs[ngram] = 0
            freqs.loc[period, ngram] += 1

Логика довольно проста: если рассматриваемый n-грамм уже виден (для него есть столбец в "freqs")"), увеличить счетчик на 1. Если его не было видно, создайте новый столбец с 0 для этого n-грамма, а затем увеличьте как обычно.В подавляющем большинстве случаев это работает нормально, но для крошечной доли н-грамма я получаю эту ошибку, когда цикл достигает строки приращения:

KeyError: u'the label [7 85.40] is not in the [index]'

(извините за отсутствие правильноготрассировка стека - я делаю это в блокноте Zeppelin, а Zeppelin не дает правильной трассировки стека.)

Немного больше отладки показало, что в этих случаях создание нового столбца завершается неудачномолча (то есть не работает, но и не возвращает исключение).

Возможно, стоит отметить, что в более ранней версии кода я использовал «loc» для непосредственного присвоения ячейке во вновь созданном столбце, а не для создания первого столбца, например:

if not ngram in freqs.columns:
    freqs.loc[period, ngram] = 1

Я изменил это, потому что это вызывало проблемы при назначении NaN для этого n-грамма всем остальным периодам, но прямое назначение задыхалось точно на тех же n-граммах, что и с новым кодом.

Обернув строку приращения в блок try / Кроме того, я обнаружил, что ошибка чрезвычайно редкая: она встречается в течение примерно 20 из общего количества более 100 000 н-грамм в корпусе.Вот несколько примеров:

"7 85.40"
"2014 july"
"2010 3.4"
"and 77"
"1997 and"
"and 2014"
"6 2008"
"879 --"
"-- 894"
"2003 -"
"- 2014"

Большинство из 20 содержат цифры, но по крайней мере одно полностью состоит из букв (два слова разделены пробелом - его нет в списке выше, потому что я перезапустилсценарий при наборе этого вопроса, и не дошел до этого момента), и большое количество n-грамм только из цифр не вызывает проблем.Большинство проблемных включают годы, что, на первый взгляд, может указывать на некоторую путаницу с DatetimeIndex объекта DataFrame (учитывая, что DatetimeIndex принимает частичные совпадения), но это не объясняет не даты, особенно те, которыеначиная с букв.

Несмотря на маловероятность конфликта DatetimeIndex, я попробовал другой метод создания каждого нового столбца (как это предлагается в ответе на Добавление нового столбца в существующий DataFrame в пандах Python ), используя «loc», чтобы избежать путаницы между строками и столбцами:

freqs.loc[:, ngram] = Series(0, index = freqs.index)

... но это встречается точно так же, как мой исходный код, который неявно создавал каждый новый столбец, присваивая non-существующий столбец:

KeyError: u'7 85.40'

Затем я попробовал метод DataFrame.assign (предложенный в том же ответе, цитированном выше, хотя мне нужно было добавить обходной путь, предложенный ответом для панд, назначаемых с новымимя столбца в виде строки ):

kwarg = {ngram: 0}
freqs = freqs.assign(**kwarg)

Увы, это точнота же ошибка.

Кто-нибудь знает, почему это может происходить?Учитывая редкость, я полагаю, я мог бы просто проигнорировать проблемные н-граммы, но было бы хорошо понять, что происходит.

Ответы [ 2 ]

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

Для моего исходного набора данных, содержащего около 3000 коротких документов, ответ jpp работал нормально, и потребовалось около 10 минут для запуска в Zeppelin на сервере, где я выполняю тестирование - что было на порядокбыстрее, чем код, который я использовал (в дополнение к решению проблемы KeyError).Однако, когда я попробовал больший набор данных, около 10000, код не закончил после 18 часов работы - я подозреваю, что это было связано с сохранением всего в памяти при использовании apply (см. https://ys -l.github.io / posts / 2015/08/28 / pandas-apply-apply-apply / для некоторых размышлений по этому вопросу).

Показывая, что applyЭто был просто элегантный способ создания вложенного цикла, я решил избежать очевидных проблем с памятью, написав циклы явно, при этом все еще используя метод Series.value_counts, который должен стать источником реального повышения эффективности.Это было немного сложно из-за проблем с типами данных, но вот конечный результат:

period_index = ngrams.index.unique(level = "Period")
freqs = DataFrame()

for period in period_index:
    period_ngrams = ngrams.loc[period]
    period_freqs = DataFrame(index = period_ngrams.index)
    for i, doc in period_ngrams.iterrows():
        period_freqs = period_freqs.append(Series(doc["N-grams"]). \
                           value_counts(sort = False), ignore_index = True)
    period_sums = period_freqs.sum()
    period_sums.name = period
    freqs = freqs.append(period_sums)
    print "Processed period " + str(period) + "."

freqs["Totals"] = freqs.sum(axis = 1)
freqs = freqs.fillna(0).astype(int)

Оказывается, это не только работает с большим набором данных, но на самом деле быстрее: 5 минут на 10 000документы.

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

Вложенный цикл for не рекомендуется или не требуется.Вы можете использовать MultiLabelBinarizer из библиотеки sklearn.preprocessing для обеспечения быстрого кодирования, затем использовать groupby + sum с результатами и присоединиться к исходному фрейму данных.

Вот демонстрация:

df = df.set_index(['L1', 'L2'])

row_counts = df['values'].apply(pd.Series.value_counts).fillna(0).astype(int)

# alternative if above does not work
row_counts = df['values'].apply(lambda x: pd.Series(x).value_counts(sort=False))\
                         .fillna(0).astype(int)

row_counts_grouped = row_counts.groupby(level='L1').sum()

df = df.join(row_counts_grouped, how='inner')

print(df)

          values  a  b  c  d  e  g
L1 L2                             
1  1   [a, a, c]  3  2  2  1  1  0
   2   [b, c, d]  3  2  2  1  1  0
   3   [a, b, e]  3  2  2  1  1  0
2  1   [a, e, g]  1  2  1  2  2  1
   2   [b, d, d]  1  2  1  2  2  1
   3   [e, b, c]  1  2  1  2  2  1

Настройка / исходное решение

Мы не рассматриваем дублирующиеся значения в строке с этим решением:

from sklearn.preprocessing import MultiLabelBinarizer

df = pd.DataFrame([[1,1,['a','a','c']], [1,2,['b','c','d']], [1,3,['a','b','e']],
                   [2,1,['a','e','g']], [2,2,['b','d','d']], [2,3,['e','b','c']]],
                  columns=['L1', 'L2', 'values'])

df = df.set_index(['L1', 'L2'])

mlb = MultiLabelBinarizer()

onehot = pd.DataFrame(mlb.fit_transform(df['values']),
                      columns=mlb.classes_,
                      index=df.index.get_level_values('L1'))

onehot_grouped = onehot.groupby(level='L1').sum()

df = df.join(onehot_grouped, how='inner')

print(df)

          values  a  b  c  d  e  g
L1 L2                             
1  1   [a, a, c]  2  2  2  1  1  0
   2   [b, c, d]  2  2  2  1  1  0
   3   [a, b, e]  2  2  2  1  1  0
2  1   [a, e, g]  1  2  1  1  2  1
   2   [b, d, d]  1  2  1  1  2  1
   3   [e, b, c]  1  2  1  1  2  1
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...