🔤
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

🔍 高度なテキスト分析と自然言語処理

基本的なstringr操作を習得したら、より高度なテキスト分析と自然言語処理に挑戦しましょう。 現代のデータ分析では、テキストデータからの洞察抽出が重要な競争優位となります。

📊 テキストデータの統計分析

text_statistical_analysis.R
# テキストの統計的分析 library(tidyverse) library(tidytext) library(stringr) # テキストデータの統計的特徴量抽出 extract_text_features <- function(text_data) { text_data %>% mutate( # 基本統計 char_count = str_length(text), word_count = str_count(text, "\\S+"), sentence_count = str_count(text, "[.!?]+"), # 複雑性指標 avg_word_length = char_count / word_count, avg_sentence_length = word_count / sentence_count, # 読みやすさスコア(Flesch Reading Ease近似) readability = 206.835 - (1.015 * avg_sentence_length) - (84.6 * avg_word_length), # 感情分析指標 exclamation_ratio = str_count(text, "!") / sentence_count, question_ratio = str_count(text, "\\?") / sentence_count, # 大文字使用率 uppercase_ratio = str_count(text, "[A-Z]") / char_count, # 数値含有率 digit_ratio = str_count(text, "\\d") / char_count ) } # N-gramとキーワード分析 analyze_text_patterns <- function(text_data, n = 2) { text_data %>% unnest_tokens(word, text) %>% anti_join(stop_words) %>% # N-gram生成 mutate(ngram = paste(word, lead(word, n-1), sep = " ")) %>% count(ngram, sort = TRUE) %>% head(20) }

🧠 感情分析と意見マイニング

sentiment_analysis.R
# 感情分析と意見マイニング library(tidyverse) library(tidytext) library(textdata) # 多次元感情分析 comprehensive_sentiment <- function(text_data) { # AFINNレキシコン(-5から+5の感情スコア) afinn_sentiment <- text_data %>% unnest_tokens(word, text) %>% inner_join(get_sentiments("afinn")) %>% group_by(document_id) %>% summarise( afinn_score = sum(value), afinn_mean = mean(value), .groups = "drop" ) # NRCレキシコン(感情カテゴリ) nrc_emotions <- text_data %>% unnest_tokens(word, text) %>% inner_join(get_sentiments("nrc")) %>% count(document_id, sentiment) %>% pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) # 結果の統合 left_join(afinn_sentiment, nrc_emotions, by = "document_id") } # トピックモデリング準備 prepare_topic_modeling <- function(text_data) { text_data %>% unnest_tokens(word, text) %>% anti_join(stop_words) %>% filter(str_length(word) > 3) %>% count(document_id, word) %>% cast_dtm(document_id, word, n) }

🔍 高度な正規表現パターン

advanced_regex_patterns.R
# 実用的な高度な正規表現パターン library(stringr) # ビジネスデータ抽出パターン business_patterns <- list( # 日本の郵便番号(123-4567形式) postal_code = "\\d{3}-\\d{4}", # 電話番号(複数形式対応) phone = "(\\d{2,4}[-\\s]?\\d{2,4}[-\\s]?\\d{4}|\\d{10,11})", # メールアドレス email = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", # URL(http/https) url = "https?://[\\w\\.-]+(?:/[\\w\\.-]*)*(?:\\?[\\w&=%\\.-]*)?(?:#[\\w\\.-]*)?", # 金額(円、カンマ区切り) amount_yen = "¥?[0-9,]+円?", # 日付(YYYY/MM/DD, YYYY-MM-DD形式) date = "\\d{4}[/-]\\d{1,2}[/-]\\d{1,2}", # IPアドレス ip_address = "\\b(?:[0-9]{1,3}\\.){3}[0-9]{1,3}\\b" ) # データクリーニング関数 clean_business_data <- function(text_vector) { text_vector %>% # 全角数字を半角に変換 str_replace_all("[0-9]", function(x) as.character(utf8ToInt(x) - utf8ToInt("0") + utf8ToInt("0"))) %>% # 全角英字を半角に変換 str_replace_all("[A-Za-z]", function(x) intToUtf8(utf8ToInt(x) - utf8ToInt("A") + utf8ToInt("A"))) %>% # 不要な空白を削除 str_trim() %>% str_squish() } # エンティティ抽出関数 extract_entities <- function(text, pattern_name) { pattern <- business_patterns[[pattern_name]] str_extract_all(text, pattern) %>% map(~ if(length(.x) > 0) .x else NA_character_) }

🚀 テキストマイニングワークフロー

text_mining_workflow.R
# 包括的なテキストマイニングワークフロー library(tidyverse) library(tidytext) library(wordcloud) # 統合テキスト分析パイプライン text_mining_pipeline <- function(data, text_column) { # Step 1: データクリーニング cleaned_data <- data %>% mutate( text_clean = {{text_column}} %>% str_to_lower() %>% str_remove_all("[^\\w\\s]") %>% str_squish() ) # Step 2: トークン化と基本統計 tokens <- cleaned_data %>% unnest_tokens(word, text_clean) %>% anti_join(stop_words) # Step 3: 頻度分析 word_freq <- tokens %>% count(word, sort = TRUE) %>% mutate(proportion = n / sum(n)) # Step 4: TF-IDF分析 tfidf <- tokens %>% count(document_id, word) %>% bind_tf_idf(word, document_id, n) # Step 5: 感情分析 sentiment <- comprehensive_sentiment(cleaned_data) list( cleaned_data = cleaned_data, word_freq = word_freq, tfidf = tfidf, sentiment = sentiment ) }

💡 実践的アドバイス

🔍 テキスト分析の成功法則

  • ドメイン知識を活用:業界特有の用語や表現を理解する
  • データ品質を重視:ノイズの多いテキストは前処理が鍵
  • 多角的なアプローチ:複数の手法を組み合わせて検証
  • 可視化で洞察を深める:ワードクラウドやネットワーク図を活用
  • 継続的な改善:結果をフィードバックして手法を改良

これらの高度なテキスト分析技法により、文書データから価値ある洞察を効率的に抽出できるようになります。 次の章では、カテゴリカルデータと関数型プログラミングの活用法を学びましょう。