🔤
CHAPTER 05

テキスト処理の魔法

stringrで文字列を自在に操る。正規表現の奥深い世界、パターンマッチングの技術、テキストマイニングの神秘を解き明かし、データに隠された言葉の力を解放しよう。

stringrとは?

stringrは、文字列操作のための統一されたインターフェースを提供するパッケージです。複雑な文字列処理を直感的で読みやすいコードで実現し、テキストデータの隠れた価値を発見します。

現代のデータサイエンスでは、構造化されていないテキストデータが急増しています。SNSの投稿、レビューコメント、ログファイル、Webスクレイピングデータ...。stringrは、これらすべてを分析可能な形に変換する強力な武器です。

stringr_setup.R
# stringrの読み込み(tidyverseに含まれる) library(stringr) library(dplyr) # stringrの基本理念: # - 関数名が一貫している(str_で始まる) # - 最初の引数は常に文字列 # - 正規表現をサポート # - ベクトル化に対応

基本的な文字列操作

stringrは豊富な文字列操作関数を提供します。検索、置換、分割、結合...。すべての操作が直感的で、コードの可読性を高めます。

str_detect()

文字列にパターンが含まれているかを検出。論理値を返します。

str_detect(string, pattern)

str_extract()

パターンにマッチする部分を抽出。最初のマッチのみを返します。

str_extract(string, pattern)

str_replace()

パターンにマッチする部分を置換。最初のマッチのみを置換します。

str_replace(string, pattern, replacement)

str_split()

パターンで文字列を分割。リストまたは行列を返します。

str_split(string, pattern)

str_length()

文字列の長さを取得。NAは正しく処理されます。

str_length(string)

str_trim()

文字列の前後の空白を削除。データクリーニングに必須です。

str_trim(string, side = "both")

📧 実践例:メールアドレスの処理

顧客データベースのメールアドレスを分析・整理する実例を見てみましょう。

email_processing.R
# 顧客メールアドレスのサンプルデータ customer_data <- data.frame( id = 1:6, email = c( " tanaka@example.com ", "SATO@GMAIL.COM", "yamada.taro@company.co.jp", "invalid-email", "suzuki123@outlook.com", "" ) ) # 1. データクリーニング cleaned_data <- customer_data %>% mutate( # 前後の空白を削除 email_clean = str_trim(email), # 小文字に統一 email_lower = str_to_lower(email_clean), # 有効なメールアドレスかチェック is_valid = str_detect(email_lower, "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"), # ドメイン部分を抽出 domain = str_extract(email_lower, "@(.+)$") %>% str_remove("@"), # ユーザー名部分を抽出 username = str_extract(email_lower, "^[^@]+") ) print(cleaned_data)

🔍 メールアドレス分析結果

ID 元データ クリーニング後 有効性 ドメイン ユーザー名
1 tanaka@example.com tanaka@example.com example.com tanaka
2 SATO@GMAIL.COM sato@gmail.com gmail.com sato
3 yamada.taro@company.co.jp yamada.taro@company.co.jp company.co.jp yamada.taro
4 invalid-email invalid-email NA NA
5 suzuki123@outlook.com suzuki123@outlook.com outlook.com suzuki123
6 (空文字) (空文字) NA NA

正規表現の魔術

正規表現は、パターンマッチングの強力な言語です。複雑な文字列パターンを簡潔に表現し、高度なテキスト処理を可能にします。stringrと組み合わせることで、真の威力を発揮します。

🎯 よく使用される正規表現パターン

\\d+
数字のマッチ
\\d は任意の数字、+ は1回以上の繰り返しを意味します。
[A-Za-z]+
英字のマッチ
[] は文字クラス、A-Za-z は大文字小文字の英字を表します。
^\\w+@\\w+\\.\\w+$
メールアドレス
^ は行の開始、$ は行の終了、\\w は英数字とアンダースコアです。
\\b\\w{3,}\\b
3文字以上の単語
\\b は単語境界、{3,} は3回以上の繰り返しを意味します。

🔍 テキストマイニング実践

顧客レビューデータから感情分析や重要な情報を抽出する例を見てみましょう。

text_mining_example.R
# 顧客レビューデータ reviews <- data.frame( id = 1:5, text = c( "この商品は素晴らしい!価格も¥2,980と手頃で、品質も最高です。★★★★★", "配送が遅くてがっかりしました。3週間も待たされて...★★☆☆☆", "普通の商品です。特に良くも悪くもない。価格は¥1,500でした。", "最悪!返品したい!お金の無駄でした。電話番号03-1234-5678に連絡済み。", "とても満足しています♪リピート確定です!メール送信:info@example.com" ) ) # テキストマイニング処理 analyzed_reviews <- reviews %>% mutate( # 価格情報を抽出 price = str_extract(text, "¥[0-9,]+"), # 星評価を抽出 stars = str_count(text, "★"), # 感情語を検出 positive = str_detect(text, "素晴らしい|最高|満足|良い"), negative = str_detect(text, "がっかり|最悪|悪い|遅い"), # 連絡先情報を抽出 phone = str_extract(text, "\\d{2,3}-\\d{4}-\\d{4}"), email = str_extract(text, "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"), # 文字数 char_count = str_length(text), # 感情スコア算出 sentiment_score = case_when( positive & !negative ~ 1, !positive & negative ~ -1, TRUE ~ 0 ) ) print(analyzed_reviews)

📝 元テキスト

この商品は素晴らしい!価格も¥2,980と手頃で、品質も最高です。★★★★★
配送が遅くてがっかりしました。3週間も待たされて...★★☆☆☆
普通の商品です。特に良くも悪くもない。価格は¥1,500でした。

🔍 分析結果

価格: ¥2,980 星評価: 5個 ポジティブ: ✓ ネガティブ: ✗ 感情スコア: +1 価格: なし 星評価: 2個 ポジティブ: ✗ ネガティブ: ✓ 感情スコア: -1 価格: ¥1,500 星評価: 0個 ポジティブ: ✗ ネガティブ: ✗ 感情スコア: 0

🎨 高度なパターンマッチング

複雑なログファイルから重要な情報を抽出する実例を見てみましょう。

log_analysis.R
# Webサーバーログの分析 log_data <- data.frame( log_entry = c( "2023-12-01 14:23:45 192.168.1.100 GET /api/users 200 1.2ms", "2023-12-01 14:24:10 10.0.0.25 POST /api/login 401 0.8ms", "2023-12-01 14:25:33 203.104.15.200 GET /api/products 500 5.7ms", "2023-12-01 14:26:01 172.16.0.10 DELETE /api/users/123 204 2.1ms" ) ) # 正規表現でログを構造化 parsed_logs <- log_data %>% mutate( # 日時を抽出 datetime = str_extract(log_entry, "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}"), # IPアドレスを抽出 ip_address = str_extract(log_entry, "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"), # HTTPメソッドを抽出 method = str_extract(log_entry, "\\b(GET|POST|PUT|DELETE|PATCH)\\b"), # URLパスを抽出 path = str_extract(log_entry, "/[a-zA-Z0-9/_]+"), # ステータスコードを抽出 status_code = str_extract(log_entry, "\\b[1-5]\\d{2}\\b") %>% as.numeric(), # レスポンス時間を抽出 response_time = str_extract(log_entry, "\\d+\\.\\d+ms") %>% str_remove("ms") %>% as.numeric(), # エラーかどうかを判定 is_error = status_code >= 400, # IPアドレスの分類 ip_type = case_when( str_detect(ip_address, "^192\\.168\\.") ~ "Private", str_detect(ip_address, "^10\\.") ~ "Private", str_detect(ip_address, "^172\\.(1[6-9]|2[0-9]|3[01])\\.") ~ "Private", TRUE ~ "Public" ) ) print(parsed_logs)

📊 ログ分析結果

日時 IPアドレス メソッド パス ステータス 応答時間 エラー IP種別
2023-12-01 14:23:45 192.168.1.100 GET /api/users 200 1.2ms Private
2023-12-01 14:24:10 10.0.0.25 POST /api/login 401 0.8ms Private
2023-12-01 14:25:33 203.104.15.200 GET /api/products 500 5.7ms Public
2023-12-01 14:26:01 172.16.0.10 DELETE /api/users/123 204 2.1ms Private