web-dev-qa-db-ja.com

Q:[パンダ]非常に大きなdfの名前に基づいて複数のエントリを持つ個人に一意のIDを効率的に割り当てる方法

それぞれが複数のエントリを持つさまざまな一意の個人のデータセットを取得し、各個人にすべてのエントリの一意のIDを割り当てたいと思います。 dfの例を次に示します。

      FirstName LastName  id
0     Tom       Jones     1
1     Tom       Jones     1
2     David     Smith     1
3     Alex      Thompson  1
4     Alex      Thompson  1

したがって、基本的に、Tom Jonesのすべてのエントリにid = 1を、David Smithのすべてのエントリにid = 2を、Alex Thompsonのすべてのエントリにid = 3、というようにしたいです。

だから、私はすでに1つの解決策を持っています、それは2つの値(idに1つ、インデックスに1つ)を繰り返し、前の個体と一致するかどうかに基づいて個体にIDを割り当てる完全なpythonループです:

x = 1
i = 1

while i < len(df_test):
    if (df_test.LastName[i] == df_test.LastName[i-1]) & 
    (df_test.FirstName[i] == df_test.FirstName[i-1]):
        df_test.loc[i, 'id'] = x
        i = i+1
    else:
        x = x+1
        df_test.loc[i, 'id'] = x
        i = i+1

私が遭遇している問題は、データフレームに約900万のエントリがあるため、そのループでは実行に膨大な時間がかかっていたことです。誰でもこれを行うためのより効率的な方法を考えることができますか? groupbyとmultiindexingを潜在的なソリューションとして検討してきましたが、まだ適切なソリューションを見つけることができていません。ありがとう!

17
Simon Sharp

姓と名を結合し、カテゴリに変換してからコードを取得できます。

もちろん、同じ名前の複数の人は同じidを持ちます。

df = df.assign(id=(df['LastName'] + '_' + df['FirstName']).astype('category').cat.codes)
>>> df
  FirstName  LastName  id
0       Tom     Jones   0
1       Tom     Jones   0
2     David     Smith   1
3      Alex  Thompson   2
4      Alex  Thompson   2
16
Alexander

このアプローチでは、.groupby()および.ngroup()(Pandas 0.20.2の新機能))を使用してid列を作成します。

_df['id'] = df.groupby(['LastName','FirstName']).ngroup()
>>> df

   First    Second  id
0    Tom     Jones   0
1    Tom     Jones   0
2  David     Smith   1
3   Alex  Thompson   2
4   Alex  Thompson   2
_

タイミングを確認しましたが、この例の小さなデータセットの場合、Alexanderの答えは高速です。

_%timeit df.assign(id=(df['LastName'] + '_' + df['FirstName']).astype('category').cat.codes)
1000 loops, best of 3: 848 µs per loop

%timeit df.assign(id=df.groupby(['LastName','FirstName']).ngroup())
1000 loops, best of 3: 1.22 ms per loop
_

ただし、より大きなデータフレームの場合、groupby()アプローチのほうが高速に見えます。大規模な代表的なデータセットを作成するには、fakerを使用して5000名のデータフレームを作成し、最初の2000名をこのデータフレームに連結して、7000名のデータフレームを作成しました。

_import faker
fakenames = faker.Faker()
first = [ fakenames.first_name() for _ in range(5000) ]
last = [ fakenames.last_name() for _ in range(5000) ]
df2 = pd.DataFrame({'FirstName':first, 'LastName':last})
df2 = pd.concat([df2, df2.iloc[:2000]])
_

この大きなデータセットでタイミングを実行すると、次の結果が得られます。

_%timeit df2.assign(id=(df2['LastName'] + '_' + df2['FirstName']).astype('category').cat.codes)
100 loops, best of 3: 5.22 ms per loop

%timeit df2.assign(id=df2.groupby(['LastName','FirstName']).ngroup())
100 loops, best of 3: 3.1 ms per loop
_

データセットで両方のアプローチをテストして、データのサイズを考慮してどちらが最適に機能するかを判断することができます。

26
Craig

このメソッドにより、「id」列名を変数で定義できます。さらに、assignメソッドまたはgroupbyメソッドに比べて読みやすくなっています。

# Create Dataframe
df = pd.DataFrame(
    {'FirstName': ['Tom','Tom','David','Alex','Alex'],
    'LastName': ['Jones','Jones','Smith','Thompson','Thompson'],
    })

newIdName = 'id'   # Set new name here.

df[newIdName] = (df['LastName'] + '_' + df['FirstName']).astype('category').cat.codes     

出力:

>>> df
          FirstName  LastName  id
        0       Tom     Jones   0
        1       Tom     Jones   0
        2     David     Smith   1
        3      Alex  Thompson   2
        4      Alex  Thompson   2
1
DougR