【Pandas】テキストデータ処理を極める!strアクセサと代替手法

2025-06-06

pandasは数値データだけでなく、テキスト(文字列)データを扱うための強力な機能も提供しています。これにより、データフレームやSeries内の文字列を効率的に操作、クリーニング、分析することができます。

主にSeries.strアクセサを通じて、文字列操作メソッドにアクセスします。これはPythonの標準的な文字列メソッド(str.lower(), str.contains()など)に似ていますが、pandasのSeries全体にわたってベクトル化された操作を可能にします。

Series.str アクセサ

文字列メソッドを使用するには、まずSeriesに.strアクセサをつけます。 例えば、df['column_name'].str.lower() のように使用します。

よく使われるメソッド

  • str.cat(others=None, sep=None, na_rep=None): Series内の文字列を結合します。

    s1 = pd.Series(['A', 'B'])
    s2 = pd.Series(['X', 'Y'])
    print(s1.str.cat(s2, sep='-'))
    # 0    A-X
    # 1    B-Y
    # dtype: object
    
  • str.get(i): 各文字列のi番目の要素(文字)を取得します。

    s_chars = pd.Series(['hello', 'world'])
    print(s_chars.str.get(0))
    # 0    h
    # 1    w
    # dtype: object
    
  • str.split(pat=None, n=-1, expand=False): 指定した区切り文字で文字列を分割します。

    • expand=True の場合、分割された要素ごとに新しいカラムを持つDataFrameを返します。
    • expand=False の場合、リストのSeriesを返します。
    s_text = pd.Series(['a,b,c', 'd,e'])
    print(s_text.str.split(','))
    # 0    [a, b, c]
    # 1       [d, e]
    # dtype: object
    print(s_text.str.split(',', expand=True))
    #    0  1    2
    # 0  a  b    c
    # 1  d  e  None
    
  • str.extract(pattern, expand=True): 正規表現を使用して、文字列からグループを抽出します。

    • expand=True の場合、抽出されたグループごとに新しいカラムを持つDataFrameを返します。
    • expand=False の場合、Seriesを返します。
    s_codes = pd.Series(['ABC-123', 'DEF-456', 'GHI-789'])
    extracted = s_codes.str.extract(r'([A-Z]+)-(\d+)')
    print(extracted)
    #      0    1
    # 0  ABC  123
    # 1  DEF  456
    # 2  GHI  789
    
  • str.replace(pattern, repl, regex=True): マッチしたパターンを別の文字列で置き換えます。

    print(s_fruits.str.replace('juice', 'soda'))
    # 0      apple pie
    # 1   banana shake
    # 2     orange soda
    # 3           grape
    # dtype: object
    
  • str.startswith(prefix) / str.endswith(suffix): 指定された文字列で始まるか、終わるかをブール値で返します。

  • str.contains(pattern, case=True, na=None, regex=True): 各文字列が指定されたパターン(正規表現も可)を含むかどうかをブール値で返します。

    • case=False で大文字・小文字を区別しない検索が可能です。
    • na=False とすると、NaN 値を False として扱います。
    s_fruits = pd.Series(['apple pie', 'banana shake', 'orange juice', 'grape'])
    print(s_fruits.str.contains('apple'))
    # 0     True
    # 1    False
    # 2    False
    # 3    False
    # dtype: bool
    print(s_fruits.str.contains('A', case=False)) # 大文字・小文字を区別しない
    # 0     True
    # 1     True
    # 2     True
    # 3    False
    # dtype: bool
    
  • str.strip() / str.lstrip() / str.rstrip(): 文字列の先頭/末尾/両端の空白(または指定した文字)を削除します。

    s_space = pd.Series(['  hello  ', 'world '])
    print(s_space.str.strip())
    # 0    hello
    # 1    world
    # dtype: object
    
  • str.len(): 各文字列の長さを返します。

    print(s.str.len())
    # 0    5
    # 1    6
    # 2    6
    # dtype: int64
    
  • str.title(): 各単語の最初の文字を大文字に変換します。

  • str.capitalize(): 各文字列の最初の文字を大文字に、残りを小文字に変換します。

  • str.lower() / str.upper(): 全ての文字列を小文字/大文字に変換します。

    import pandas as pd
    s = pd.Series(['Apple', 'Banana', 'ORANGE'])
    print(s.str.lower())
    # 0     apple
    # 1    banana
    # 2    orange
    # dtype: object
    

NaN値の扱い

テキスト操作メソッドは、NaN(欠損値)を適切に扱います。多くの場合、NaNが入力された場合は出力もNaNとなります。str.contains() のように na=None がデフォルトの引数を持つ場合、NaN 値はそのまま NaN として残りますが、na=False のように指定することでNaNを特定のブール値として扱うことができます。

ベクトル化された操作

Series.strを使用する最大の利点は、操作がベクトル化されていることです。これは、ループを使用せずにSeries全体にわたって効率的に操作が適用されることを意味します。大量のテキストデータを扱う際に非常にパフォーマンスが高いです。



pandasでテキストデータを扱う際には、いくつかの一般的なエラーや予期せぬ挙動に遭遇することがあります。ここでは、それらの原因と解決策について解説します。

Series.strアクセサの使用忘れ (AttributeError: Can only use .str accessor with string values!)

エラーメッセージの例

AttributeError: Can only use .str accessor with string values!

または

AttributeError: 'Series' object has no attribute 'str'

原因
これは最もよくあるエラーの一つです。strアクセサを使用せずに、数値型やブール型などの文字列ではないSeriesに対して文字列メソッドを呼び出そうとした場合に発生します。例えば、df['数値カラム'].lower() のようにしてしまうとこのエラーになります。

トラブルシューティング
操作しようとしているSeriesのデータ型(dtype)を確認してください。

import pandas as pd
df = pd.DataFrame({'text': ['apple', 'banana', 'cherry'],
                   'number': [1, 2, 3]})

print(df['number'].dtype) # int64

# 誤った例 (エラーになる)
# df['number'].str.lower()

# 正しい例 (textカラムはstrアクセサを使用できる)
print(df['text'].str.upper())

dtypeobject型(文字列を含む可能性が高い)であることを確認し、文字列操作を行うSeriesに.strをつけてください。

非文字列データ型におけるNaN値の存在 (TypeErrorまたは予期せぬ挙動)

問題
文字列として扱いたいカラムに数値型のNaNnp.nan)が含まれている場合、文字列操作がうまくいかないことがあります。特に、dtypeobjectではない(例:float64)場合に発生しやすいです。

トラブルシューティング

  1. import numpy as np
    s = pd.Series(['text_a', np.nan, 'text_c'])
    print(s.dtype) # object
    
    # NaNがfloatとして扱われている場合
    s_mixed = pd.Series(['text_a', 123, np.nan])
    print(s_mixed.dtype) # object
    # この場合もstrアクセサは使えますが、数字が混じると後述のTypeErrorにつながることがあります。
    
  2. NaNを空文字列に変換
    文字列操作を行う前に、NaNを空文字列('')に変換すると、後続の文字列操作がスムーズになります。

    s_with_nan = pd.Series(['apple', np.nan, 'orange'])
    print(s_with_nan.str.lower()) # NaNはそのままNaNになる
    
    s_filled = s_with_nan.fillna('')
    print(s_filled)
    print(s_filled.str.lower())
    # 0     apple
    # 1          
    # 2    orange
    # dtype: object
    

正規表現の誤り (re.errorまたは意図しない結果)

問題
str.contains(), str.replace(), str.extract() などで正規表現を使用する際、正規表現の構文が間違っているとエラーになるか、期待通りの結果が得られないことがあります。

エラーメッセージの例

re.error: bad character range ...

トラブルシューティング

  1. 正規表現の確認
    使用している正規表現が正しい構文であるかを確認してください。正規表現チェッカーのオンラインツール(例: regex101.com)を利用すると便利です。

    # 誤った正規表現の例 (エスケープが必要)
    # s.str.contains('a.b') # '.' は正規表現で「任意の一文字」を意味するため、"acb"などもマッチする
    # 正しい例 (リテラルな'.'をマッチさせるにはエスケープ)
    s = pd.Series(['a.b', 'acb'])
    print(s.str.contains(r'a\.b'))
    # 0     True
    # 1    False
    # dtype: bool
    
  2. raw文字列リテラル (r'') の使用
    バックスラッシュ(\)を多く含む正規表現では、Pythonのエスケープシーケンスと混同しないように、raw文字列リテラル(r'your_regex_here')を使用することを強く推奨します。

文字列エンコーディングの問題 (UnicodeDecodeErrorなど)

問題
ファイルからテキストデータを読み込む際や、異なるシステム間でデータをやり取りする際に、エンコーディングの不一致により文字化けやUnicodeDecodeErrorが発生することがあります。

エラーメッセージの例

UnicodeDecodeError: 'utf-8' codec can't decode byte ...

トラブルシューティング

  1. ファイル読み込み時のencoding指定
    pd.read_csv()pd.read_excel()などでファイルを読み込む際に、encoding引数で適切なエンコーディングを指定します。日本のシステムでは'shift_jis', 'cp932', 'euc_jp' などがよく使われますが、最近は'utf-8'が主流です。

    # pd.read_csv('your_file.csv', encoding='shift_jis')
    # pd.read_csv('your_file.csv', encoding='utf-8')
    
  2. 現在の環境のデフォルトエンコーディング確認
    Pythonのデフォルトエンコーディングを確認することも役立ちます。

    import sys
    print(sys.getdefaultencoding())
    
  3. エラーハンドリング
    エンコーディングが特定できない場合、errors='ignore'errors='replace'などの引数を指定して、デコードエラーをスキップまたは置換することも可能ですが、データの品質に影響を与える可能性があります。

inplace=Trueの誤解または非推奨

問題
過去のpandasバージョンでは、多くのメソッドにinplace=True引数がありましたが、現在では非推奨または存在しないメソッドが増えています。これを誤って使用しようとするとエラーになるか、何も変更されないことがあります。

トラブルシューティング
ほとんどのpandasメソッドは、操作結果として新しいSeriesやDataFrameを返します。したがって、結果を元の変数に再代入する形式で記述するのが一般的です。

# 以前の書き方 (非推奨または存在しない場合がある)
# df['text_col'].str.lower(inplace=True)

# 現在の推奨される書き方
df['text_col'] = df['text_col'].str.lower()

期待通りの結果が得られない(論理的なエラー)

問題
エラーは出ないが、str.contains()が期待通りにマッチしない、str.replace()が置き換わらない、str.split()で正しく分割されないなど、論理的な問題が発生することがあります。

トラブルシューティング

  1. 入力データの確認
    対象のSeriesの実際の値を確認し、空白、大文字・小文字、特殊文字などが意図通りであるかを確認します。

    print(df['text_col'].head())
    
  2. 大文字・小文字の区別
    str.contains()str.replace()などで大文字・小文字を区別しない検索を行いたい場合は、case=Falseを指定します。

    s = pd.Series(['Apple', 'Banana'])
    print(s.str.contains('apple', case=False))
    
  3. 余分な空白の除去
    文字列の先頭や末尾に余分な空白があるためにマッチしないことがあります。str.strip()で取り除いてから操作を行います。

    s_space = pd.Series(['  data ', ' value'])
    print(s_space.str.contains('data')) # False (先頭に空白があるため)
    print(s_space.str.strip().str.contains('data')) # True
    
  4. 正規表現がGreedy/Non-Greedyであることの理解
    str.extract()などで正規表現を使う場合、*+などの量指定子がデフォルトで「Greedy(可能な限り長くマッチ)」であるため、意図しないマッチングが起こることがあります。? を付けてNon-Greedyにすると、最短でマッチします。

  5. str.split()のexpand引数
    分割後の結果をDataFrameとして得たい場合はexpand=Trueを指定します。そうでない場合はリストのSeriesが返されます。



準備: サンプルデータの作成

まず、テキストデータを含むpandas Seriesを作成します。

import pandas as pd
import numpy as np

# サンプルデータ
data = {
    'product_name': [
        '  Apple iPhone 15 (Blue) ',
        'Samsung Galaxy S24 Ultra (Black)',
        'Google Pixel 8 Pro (Obsidian)',
        'Xiaomi 14 Ultra',
        np.nan, # 欠損値
        'Sony Xperia 1 VI (Silver)'
    ],
    'description': [
        'High-end smartphone with A17 chip.',
        'Flagship Android phone with S Pen.',
        'AI-powered phone with amazing camera.',
        'Powerful camera phone with Leica lens.',
        'No description available.',
        'CinemaWide display, pro camera features.'
    ],
    'price_info': [
        '$999.00',
        '$1299.99',
        '$799.50',
        '$899.00',
        '$0.00',
        '$1199.00'
    ]
}
df = pd.DataFrame(data)
print("--- 元のデータフレーム ---")
print(df)
print("\n")

大文字・小文字の変換 (Case Conversion)

文字列をすべて大文字、小文字、またはタイトルの形式に変換します。

print("--- 1. 大文字・小文字の変換 ---")
# 全て小文字に変換
df['product_name_lower'] = df['product_name'].str.lower()
print("全て小文字:\n", df[['product_name', 'product_name_lower']].head())

# 全て大文字に変換
df['product_name_upper'] = df['product_name'].str.upper()
print("\n全て大文字:\n", df[['product_name', 'product_name_upper']].head())

# 各単語の最初の文字を大文字に(タイトルケース)
df['product_name_title'] = df['product_name'].str.title()
print("\nタイトルケース:\n", df[['product_name', 'product_name_title']].head())
print("-" * 30 + "\n")

空白の除去 (Stripping Whitespace)

文字列の先頭、末尾、または両端から空白文字を除去します。

print("--- 2. 空白の除去 ---")
# product_nameカラムは先頭・末尾に空白があるため、str.strip()で除去
df['product_name_stripped'] = df['product_name'].str.strip()
print("空白除去後:\n", df[['product_name', 'product_name_stripped']].head())

# product_nameカラムを更新して、以降の操作に空白除去済みデータを使用
df['product_name'] = df['product_name'].str.strip()
print("-" * 30 + "\n")

文字列の長さ (String Length)

各文字列の文字数を取得します。

print("--- 3. 文字列の長さ ---")
df['name_length'] = df['product_name'].str.len()
print("製品名の長さ:\n", df[['product_name', 'name_length']])
print("-" * 30 + "\n")

文字列の検索・フィルタリング (contains, startswith, endswith)

特定の文字列パターンを含むか、特定の文字列で始まる/終わるかを調べます。

print("--- 4. 文字列の検索・フィルタリング ---")
# 'Samsung'を含む製品名を抽出
samsung_phones = df[df['product_name'].str.contains('Samsung', na=False)]
print("Samsung製品:\n", samsung_phones['product_name'])

# 'Apple'を含む製品名を抽出し、大文字・小文字を区別しない
apple_or_apple_phones = df[df['product_name'].str.contains('apple', case=False, na=False)]
print("\n'apple' (大文字小文字区別なし)を含む製品:\n", apple_or_apple_phones['product_name'])

# 'Google'で始まる製品名を抽出
google_phones = df[df['product_name'].str.startswith('Google', na=False)]
print("\n'Google'で始まる製品:\n", google_phones['product_name'])

# 'Pro'で終わる製品名を抽出
pro_versions = df[df['product_name'].str.endswith('Pro', na=False)]
print("\n'Pro'で終わる製品:\n", pro_versions['product_name'])
print("-" * 30 + "\n")

文字列の置換 (Replacing Strings)

特定の文字列パターンを別の文字列に置き換えます。正規表現も使用できます。

print("--- 5. 文字列の置換 ---")
# 'iPhone'を'smartphone'に置換
df['product_name_replaced'] = df['product_name'].str.replace('iPhone', 'Smartphone')
print("'iPhone'を'Smartphone'に置換:\n", df[['product_name', 'product_name_replaced']])

# 正規表現を使用して、括弧内の色情報を除去
df['product_name_no_color'] = df['product_name'].str.replace(r' \(.*\)', '', regex=True)
print("\n色情報を除去:\n", df[['product_name', 'product_name_no_color']])
print("-" * 30 + "\n")

文字列の分割 (Splitting Strings)

区切り文字に基づいて文字列を分割し、リストまたは個別のカラムとして取得します。

print("--- 6. 文字列の分割 ---")
# product_nameを空白で分割し、リストとして取得
df['name_parts_list'] = df['product_name'].str.split(' ')
print("製品名を空白で分割 (リスト):\n", df[['product_name', 'name_parts_list']])

# product_nameを空白で分割し、新しいカラムとして取得
df[['brand', 'model', 'version_etc']] = df['product_name'].str.split(' ', n=2, expand=True)
print("\n製品名を空白で分割 (カラム展開):\n", df[['product_name', 'brand', 'model', 'version_etc']])

# descriptionカラムを'.'で分割
df['description_sentences'] = df['description'].str.split('.')
print("\n説明文を'.'で分割:\n", df[['description', 'description_sentences']])
print("-" * 30 + "\n")

正規表現によるデータ抽出 (extract)

正規表現のグループ機能を利用して、文字列から特定のパターンを抽出します。

print("--- 7. 正規表現によるデータ抽出 ---")
# product_nameからブランドとモデル番号(もしあれば)を抽出
# 例: 'Samsung Galaxy S24 Ultra' -> 'Samsung', 'Galaxy S24 Ultra'
# 例: 'Google Pixel 8 Pro' -> 'Google', 'Pixel 8 Pro'
extracted_info = df['product_name'].str.extract(r'^(.*?)\s(.+)$', expand=True)
df['extracted_brand'] = extracted_info[0]
df['extracted_model'] = extracted_info[1]
print("ブランドとモデルの抽出:\n", df[['product_name', 'extracted_brand', 'extracted_model']])

# price_infoから数値部分と通貨記号を抽出
# \$ (リテラルの$)と (\d+\.\d+) (数字.数字)をグループ化
price_parts = df['price_info'].str.extract(r'(\$)(\d+\.\d+)', expand=True)
df['currency_symbol'] = price_parts[0]
df['numeric_price'] = price_parts[1].astype(float) # 抽出した文字列を数値に変換
print("\n価格情報の抽出:\n", df[['price_info', 'currency_symbol', 'numeric_price']])
print("-" * 30 + "\n")

個々の要素へのアクセス (get)

分割されたリストや文字列から、特定のインデックスの要素を取得します。

print("--- 8. 個々の要素へのアクセス ---")
# product_name_lowerの最初の文字を取得
df['first_char'] = df['product_name_lower'].str.get(0)
print("製品名の最初の文字:\n", df[['product_name', 'first_char']])

# 分割したリストの最初の要素を取得
df['first_word'] = df['name_parts_list'].str.get(0)
print("\n製品名の最初の単語:\n", df[['product_name', 'first_word']])
print("-" * 30 + "\n")

文字列の結合 (cat)

Series内の文字列を結合します。

print("--- 9. 文字列の結合 ---")
# product_nameとdescriptionを結合
df['combined_info'] = df['product_name'].str.cat(df['description'], sep=' - ')
print("製品名と説明文の結合:\n", df[['product_name', 'description', 'combined_info']])

# 複数のカラムを結合 (より複雑な結合)
# df['full_product_info'] = df['product_name'].str.cat(
#     [df['description'], df['price_info'].astype(str)], # 数値型はstrに変換
#     sep=' | '
# )
# print("\n複数カラムの結合:\n", df[['product_name', 'description', 'price_info', 'full_product_info']])
print("-" * 30 + "\n")

ダミー変数の作成 (get_dummies)

カテゴリカルなテキストデータからダミー変数を作成します。

print("--- 10. ダミー変数の作成 ---")
# product_nameのブランドからダミー変数を作成
# brandカラムを作成済みとして使用
brand_dummies = pd.get_dummies(df['brand'], prefix='brand')
print("ブランドのダミー変数:\n", brand_dummies.head())

# 元のデータフレームに結合
df = pd.concat([df, brand_dummies], axis=1)
print("\nダミー変数を結合したデータフレームのヘッド:\n", df.head())
print("-" * 30 + "\n")


pandasのSeries.strアクセサはテキストデータ操作の標準的な方法ですが、状況によっては他の代替手段がより適切であったり、効率的であったりすることがあります。ここでは、主な代替方法とその使い分けについて解説します。

Pythonの標準的な文字列メソッド(apply()とラムダ関数)

メリット

  • デバッグのしやすさ
    Pythonの通常の関数としてロジックをテストしやすいです。
  • 柔軟性
    任意のPythonコードを実行できるため、Series.strが直接サポートしない複雑なロジックを実装できます。

デメリット

  • 欠損値のハンドリング
    str.lower()などのPythonの標準メソッドはNoneNaNに対してはエラーを発生させるため、apply()を使用する場合は明示的に欠損値のチェックを行う必要があります。
  • パフォーマンス
    大量のデータに対しては、通常Series.strアクセサよりも処理が遅くなる傾向があります。Series.strはC/Cythonで最適化されており、ベクトル化された操作を提供するためです。

使用例
特定の条件に基づいて文字列を変換したり、複数の操作を一度に行う場合など。

import pandas as pd
import numpy as np

s = pd.Series(['  Apple iPhone ', 'Samsung Galaxy', np.nan, 'Google Pixel'])

print("--- apply()とラムダ関数 ---")

# 欠損値を考慮して小文字に変換
s_lower_apply = s.apply(lambda x: x.lower() if isinstance(x, str) else x)
print("小文字変換 (apply):\n", s_lower_apply)

# 特定の文字が含まれていたら別の文字列に変換するカスタムロジック
s_custom_transform = s.apply(lambda x: x.strip().upper() if isinstance(x, str) and 'Apple' in x else x)
print("\nカスタム変換 (apply):\n", s_custom_transform)
print("-" * 30 + "\n")

リスト内包表記 (List Comprehensions)

メリット

  • 柔軟性
    apply()と同様に、カスタムロジックを適用できます。
  • 可読性
    シンプルな操作であれば、簡潔に記述できます。

デメリット

  • 欠損値のハンドリング
    明示的な欠損値チェックが必要です。
  • パフォーマンス
    apply()と同様に、Series.strに比べて遅いことが多いです。

使用例
非常に大規模なデータでない場合や、特定のPythonリスト操作と組み合わせる場合に検討されます。

import pandas as pd
import numpy as np

s = pd.Series(['  Apple iPhone ', 'Samsung Galaxy', np.nan, 'Google Pixel'])

print("--- リスト内包表記 ---")

# 欠損値を考慮して空白除去と大文字変換
s_processed_lc = pd.Series([
    x.strip().upper() if isinstance(x, str) else x
    for x in s
])
print("空白除去&大文字変換 (リスト内包表記):\n", s_processed_lc)
print("-" * 30 + "\n")

Cython/Numbaなどによる高速化

メリット

  • パフォーマンス
    Pythonのループ処理を劇的に高速化できます。

デメリット

  • 複雑性
    コードの記述が複雑になる可能性があります。
  • 学習コスト
    CythonやNumbaの知識が必要です。

使用例
自然言語処理の前処理で、カスタムのトークナイザーや正規化関数を非常に高速に適用したい場合など。これは一般的なユースケースではなく、特定のパフォーマンス要件がある場合にのみ検討されます。

# CythonやNumbaの具体的なコード例は複雑になるため割愛しますが、
# 例えばNumbaを使用する場合の概念的なイメージ:

# from numba import jit
# @jit(nopython=True)
# def fast_text_processor(text_list):
#     results = []
#     for text in text_list:
#         if isinstance(text, str):
#             results.append(text.lower().replace('apple', 'banana'))
#         else:
#             results.append(text)
#     return results

# s_processed_numba = pd.Series(fast_text_processor(s.tolist()))

正規表現モジュール re の直接利用

メリット

  • 正規表現機能のフル活用
    reモジュールが提供する全ての機能(例: re.subnでの置換回数取得、re.finditerでの全マッチングのイテレートなど)を利用できます。

デメリット

  • 記述の複雑性
    reモジュール関数の引数にre.IGNORECASEなどのフラグを渡す必要があります。
  • パフォーマンス
    apply()を介して使用する場合、Series.strに比べて遅くなります。

使用例
複数のマッチングを一度に処理したい場合や、複雑なコールバック関数を正規表現の置換に適用したい場合など。

import pandas as pd
import re
import numpy as np

s = pd.Series(['Item_A_123', 'Item_B_456_V2', np.nan, 'Product_C'])

print("--- reモジュールの直接利用 (applyと組み合わせ) ---")

# 正規表現で数字部分を抽出する関数
def extract_number(text):
    if pd.isna(text): # 欠損値のチェック
        return None
    match = re.search(r'(\d+)', text)
    return int(match.group(1)) if match else None

s_extracted_re = s.apply(extract_number)
print("数字部分の抽出 (re.search):\n", s_extracted_re)

# 特定のパターンを別の文字列に置換し、置換回数も取得
def replace_and_count(text):
    if pd.isna(text):
        return text, 0
    new_text, count = re.subn(r'Item_', 'PRODUCT_', text)
    return new_text, count

# 複数の戻り値をSeriesに適用すると、タプルのSeriesになる
s_replaced_re = s.apply(replace_and_count)
print("\n置換と置換回数 (re.subn):\n", s_replaced_re)
print("-" * 30 + "\n")

専門的なテキスト処理ライブラリ

メリット

  • 最適化
    特定のタスクに最適化されたアルゴリズムとデータ構造が提供されます。
  • 高度な機能
    NLPに特化した豊富な機能が利用できます。

デメリット

  • 依存関係の追加
    環境設定が複雑になることがあります。
  • 追加の学習コスト
    各ライブラリのAPIと概念を学ぶ必要があります。
  • scikit-learn
    Bag-of-WordsやTF-IDFなどのテキスト特徴量生成、テキスト分類モデルの構築。
  • NLTK/spaCy
    テキストのトークン化、ストップワード除去、ステミング/レンマ化、品詞タグ付け、固有表現認識など。
# spaCyを使った例 (インストールが必要: pip install spacy && python -m spacy download en_core_web_sm)
try:
    import spacy
    nlp = spacy.load("en_core_web_sm")

    text_series = pd.Series(['Apple acquired a startup.', 'Google announced new AI.', 'This is a test.'])

    def extract_entities(text):
        if pd.isna(text):
            return []
        doc = nlp(text)
        return [(ent.text, ent.label_) for ent in doc.ents]

    text_series_entities = text_series.apply(extract_entities)
    print("--- 専門ライブラリ (spaCyによる固有表現抽出) ---")
    print(text_series_entities)
    print("-" * 30 + "\n")

except ImportError:
    print("spaCyがインストールされていないか、モデルがダウンロードされていません。\n"
          "pip install spacy && python -m spacy download en_core_web_sm を実行してください。\n")

適切な方法の選択

  • NLPの専門的なタスク
    NLTK, spaCyなどの専門ライブラリを使用します。
  • 極端なパフォーマンス要件
    Cython/Numbaを検討しますが、これは高度なケースです。
  • 非常に複雑な正規表現操作
    apply()reモジュールを直接組み合わせることを検討します。
  • カスタムロジックや複雑な条件分岐
    apply()とラムダ関数(または通常の関数)を使用します。欠損値のハンドリングに注意が必要です。
  • ほとんどの場合
    Series.strアクセサを使用します。パフォーマンスと記述の簡潔さのバランスが最も優れています。