15
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

ChatGPTやClaude、GitHub CopilotのようなAIアシスタントが普及して、コードを書くハードルはかなり下がりました。
「pandasでこのデータを処理して」と投げれば、それなりのコードがすぐ返ってきます。たしかに便利です。

ただ、実務でそのまま使うには危ないコードも少なくありません。
動くけれど遅い。動くけれどメモリを食う。動くけれど結果が怪しい。
こういう“静かな地雷”が、AI生成コードには普通に混ざります。

この記事では、pandas / NumPy でデータ処理をするときに、AIが出してきたら一度止めたいコードを10個まとめます。
それぞれ「なぜ危ないのか」「どう直すのか」をセットで見ていきます。

1. iterrows() で1行ずつ回す

ratings = []
for idx, row in df.iterrows():
    if row['rating'] > 4:
        ratings.append('Excellent')
    elif row['rating'] > 2:
        ratings.append('Average')
    else:
        ratings.append('Poor')
df['evaluation'] = ratings

iterrows() は、DataFrame を1行ずつ Python オブジェクトとして取り出します。
そのため、pandas の強みであるベクトル化がほぼ効きません。データが増えるほど遅くなります。

こういう条件分岐なら、np.select でまとめて処理したほうがずっと素直です。

import numpy as np

df['evaluation'] = np.select(
    condlist=[df['rating'] > 4, df['rating'] > 2],
    choicelist=['Excellent', 'Average'],
    default='Poor'
)

行ループを書き始めたら、まず「列全体で書けないか」を疑うのがよさそうです。

2. apply() をとりあえず使う

df['price_with_tax'] = df['price'].apply(lambda x: x * 1.1)

apply() は見た目こそきれいですが、中では Python のループが回っています。
つまり、書き方が関数っぽくなっただけで、重さの本質はあまり変わりません。

単純な計算なら、そのまま演算したほうが速くて読みやすいです。

df['price_with_tax'] = df['price'] * 1.1

apply() は便利ですが、まずは「その処理、本当に必要か」を見たほうがいいです。

3. df1, df2, df3 と連番で増やす

df1 = pd.read_csv('data.csv')
df2 = df1[df1['status'] == 'active']
df3 = df2.groupby('category').sum()
df4 = df3.reset_index()
df5 = df4.rename(columns={'amount': 'total_amount'})

この書き方は、あとから見返すと何が何だかわかりにくくなります。
Notebook でセルを行き来すると、なおさら混乱しやすいです。

変数には、できるだけ中身がわかる名前をつけたほうがいいです。

active_df = pd.read_csv('data.csv').query('status == "active"')
category_totals = (
    active_df
    .groupby('category', as_index=False)['amount']
    .sum()
    .rename(columns={'amount': 'total_amount'})
)

連番よりも、処理の意味が伝わる名前のほうがずっと扱いやすいです。

4. メソッドチェーンをつなげすぎる

result = (
    pd.read_csv('sales.csv')
    .query('year == 2024')
    .assign(tax=lambda df_: df_['price'] * 0.1)
    .assign(total=lambda df_: df_['price'] + df_['tax'])
    .groupby('category', as_index=False)['total']
    .sum()
    .merge(pd.read_csv('categories.csv'), on='category')
    .sort_values('total', ascending=False)
    .reset_index(drop=True)
    .rename(columns={'total': 'total_sales', 'name': 'category_name'})
    .head(20)
)

チェーンでつなぐと一見スマートですが、長くなりすぎると追いにくくなります。
途中でエラーが出たときも、どの段階で壊れたのかを確認しづらいです。

処理のまとまりごとに分けたほうが、あとで見ても修正しやすくなります。

sales = pd.read_csv('sales.csv').query('year == 2024')
sales['tax'] = sales['price'] * 0.1
sales['total'] = sales['price'] + sales['tax']

category_totals = (
    sales
    .groupby('category', as_index=False)['total']
    .sum()
    .rename(columns={'total': 'total_sales'})
)

categories = pd.read_csv('categories.csv')
result = (
    category_totals
    .merge(categories, on='category')
    .sort_values('total_sales', ascending=False)
    .head(20)
)

「読込と加工」「集計」「結合と整形」くらいで区切ると、だいぶ見通しがよくなります。

5. SettingWithCopyWarning を無視する

import warnings
warnings.filterwarnings('ignore')

df[df['category'] == 'A']['price'] = 999

これはかなり危ない書き方です。
df[条件]['列'] のような連鎖代入は、元の DataFrame に反映される保証がありません。
Warning が出ているのは、まさにその危険を教えてくれているからです。

代入したいなら、loc を使って一発で指定します。

df.loc[df['category'] == 'A', 'price'] = 999

警告を消すのではなく、警告が出ない書き方に直すほうが安全です。

6. .loc も実は危ない

subset = df.loc[df['region'] == 'Asia']
subset.loc[subset['sales'] > 1000, 'tier'] = 'Gold'

これも、見た目ほど安全ではありません。
1回目の subset が View なのか Copy なのかが曖昧なままだと、後続の代入も不安定になります。

明示的にコピーを作るか、元の DataFrame に対して直接条件を書いたほうがわかりやすいです。

subset = df.loc[df['region'] == 'Asia'].copy()
subset.loc[subset['sales'] > 1000, 'tier'] = 'Gold'

あるいは、元データを直接更新するならこちらです。

df.loc[(df['region'] == 'Asia') & (df['sales'] > 1000), 'tier'] = 'Gold'

中間変数を作るなら、copy() を明示しておくと安心です。

7. CSVをそのまま全部読む

df = pd.read_csv('huge_data.csv')

ファイルが小さいうちはこれで十分ですが、データが大きくなると一気に苦しくなります。
不要な列まで読み、型も pandas 任せになるので、メモリをかなり食います。

必要な列だけ読み、型も先に決めておくとかなり軽くなります。

df = pd.read_csv(
    'huge_data.csv',
    usecols=['user_id', 'item_id', 'rating', 'timestamp'],
    dtype={
        'user_id': 'int32',
        'item_id': 'int32',
        'rating': 'float16',
        'timestamp': 'int64',
    }
)

列数が多いデータほど、usecolsdtype の効果は大きいです。

8. 使い終わった巨大 DataFrame を放置する

raw = pd.read_csv('10gb_data.csv', ...)
processed = heavy_transform(raw)
# raw はもう使わないのに残っている
final = another_transform(processed)

Notebook で作業していると、こういう中間データが積み上がっていきます。
気づいたらメモリ不足、というのはよくあるパターンです。

不要になった変数は消しておくと、後続処理が少し楽になります。

import gc

raw = pd.read_csv('10gb_data.csv', ...)
processed = heavy_transform(raw)

del raw
gc.collect()

final = another_transform(processed)

巨大なデータを何段もつなぐときは、どこで捨てるかも考えたほうがいいです。

9. ID カラムを文字列として扱わない

df = pd.read_csv('users.csv')
print(df['user_id'].dtype)
# float64 になることがある

欠損を含む ID は、pandas の都合で float64 扱いになってしまうことがあります。
その結果、12345671234567.0 になったり、先頭ゼロが消えたりします。

ID は数値ではなく識別子なので、最初から文字列として読むほうが安全です。

df = pd.read_csv('users.csv', dtype={'user_id': str})

あるいは、欠損を含めたまま整数として持ちたいなら、Nullable Integer 型を使います。

df = pd.read_csv('users.csv', dtype={'user_id': 'Int64'})

ID の型が崩れると、JOIN が静かに壊れるので注意が必要です。

10. concat() をループの中で何度も呼ぶ

import glob

df = pd.DataFrame()
for file in glob.glob('data/*.csv'):
    tmp = pd.read_csv(file)
    df = pd.concat([df, tmp])

これはかなり遅くなりがちな書き方です。
毎回 concat するたびに全体をコピーし直すので、ファイル数が増えるほど重くなります。

先にリストにためて、最後にまとめて結合するほうが自然です。

import glob

dfs = [pd.read_csv(f) for f in glob.glob('data/*.csv')]
df = pd.concat(dfs, ignore_index=True)

ファイル結合は、基本的に「集めてから一回でつなぐ」で覚えておくと楽です。

まとめ

パターン 何がまずいか 直し方
iterrows() で回す とにかく遅い ベクトル化 / np.select
apply() を多用する Python ループになる 演算子や組み込み関数を使う
df1, df2… と増やす 読みにくい 意味のある変数名にする
チェーンをつなげすぎる デバッグしづらい 処理単位で区切る
連鎖代入をする 反映されないことがある .loc を使う
.loc でも中間変数を作る Copy/View が曖昧 .copy() を明示する
CSV を全部読む メモリを食う usecols, dtype を指定する
中間 DF を放置する メモリが残る delgc.collect()
ID を数値のまま読む JOIN が壊れる strInt64 を使う
ループ内 concat() かなり遅い リストにためて最後に結合

おわりに

pandas 自体が悪いわけではありません。
むしろ、ちゃんと使えばかなり強力です。

問題は、AIが返してきたコードをそのまま受け入れてしまうことです。
「動く」ことと「実務で安全に使える」ことは、まったく別です。

特に大きいデータを扱うときは、
速いか、正しいか、あとで読めるか
この3つを毎回見るだけでも、事故はかなり減ります。

15
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?