これまでに
楽天モバイルが0円プランをやめたため、前回はWebスクレイピングを使って料金データを取得しました。
ただただ携帯料金会社の広告みたいな記事になるのも面白くないから、tech要素を盛り込もうとwebスクレイピングにフォーカスしたところめちゃくちゃ伸びが悪かったので、もったいつけずに結論まで駆け抜けたいと思います🏇
- 三つの情報サイトからスクレイピングして情報収集した
- 使いそうな3GB前後のプランを調べたら相場は1000円前後
- 5G, eSIMなどの条件で絞ると5プランが残った
- 楽天モバイルの新料金はそんなに高くない
私は面白半分でリンクスメイトに乗り換えました。
スクレイピングしたデータの前処理
ここからは、よくある「前処理」の話です。
今回は格安SIMを紹介しているブログ3種を情報源として使用させていただきました。
私も人のこといえませんが、紹介する人によって示されている価格が異なっているためどのサイトを情報源にしたかは明記しないで起きます。※「格安SIM」とか「楽天モバイルの代わり」とかで検索しました
前回作ったscrape_table()
関数を使用して、まとめてある料金表を拝借します。

pandas.read_html()
で返ってくるデータフレームはこんな感じ。
site1 <- scrape_table("https://あるサイト/")
site[[1]]
# 0 1
# 1 キャリア 1GB未満の 月額料金(税込)
# 2 楽天モバイル 0円
# 3 au 2,288円
# 4 ドコモ 1,980円
# 5 ソフトバンク 2,178円
# 6 povo(au) 2,728円
# 7 ahamo(ドコモ) 2,970円
# 8 LINEMO(ソフトバンク) 990円
# 2 3
# 1 1〜3GB未満の 月額料金(税込) 3〜20GB未満の 月額料金(税込)
# 2 1,078円 2,178円
# 3 2,288円 5,115円~
# 4 4,565円 5,665円~
# 5 2,178円 7,238円(無制限)
# 6 2,728円 2,728円
# 7 2,970円 2,970円
# 8 990円 2,728円
# 4
# 1 20GB以上の 月額料金(税込)
# 2 3,278円
# 3 7,238円(無制限)
# 4 7,205円(60GB)
# 5 7,238円(無制限)
# 6
# 7
# 8
データ前処理の必要がありそうなのは以下です。
- column namesが入っていない
- x~yGBというデータ範囲列が分かれているのをピボットしたい
- キャリア名に揺らぎがありそう
- 金額がx,xxx円(+表記揺らぎ)となっているが、numericにしたい
いっこずつ対処していきます。
column namesの指定
データを見る感じ、列名が一行目に入ってしまったようです。 なので、一行目を列名にしつつ、一行目データを消します。
Base pipeが苦手とするプレースホルダーの使い方のため、pipebindを使用しました。
Pipebindの使い方は以前に解説記事を書いています。
site1[[1]] |>
pipebind::bind(x, set_colnames(x, x[1,])) |>
slice(-1)
# キャリア 1GB未満の 月額料金(税込)
# 1 楽天モバイル 0円
# 2 au 2,288円
# 3 ドコモ 1,980円
# 4 ソフトバンク 2,178円
# 5 povo(au) 2,728円
# 6 ahamo(ドコモ) 2,970円
# 7 LINEMO(ソフトバンク) 990円
# 1〜3GB未満の 月額料金(税込) 3〜20GB未満の 月額料金(税込)
# 1 1,078円 2,178円
# 2 2,288円 5,115円~
# 3 4,565円 5,665円~
# 4 2,178円 7,238円(無制限)
# 5 2,728円 2,728円
# 6 2,970円 2,970円
# 7 990円 2,728円
# 20GB以上の 月額料金(税込)
# 1 3,278円
# 2 7,238円(無制限)
# 3 7,205円(60GB)
# 4 7,238円(無制限)
# 5
# 6
# 7
データをピボットしてtidyにする
さっきのテーブル1〜3GB未満の 月額料金(税込)
, 3〜20GB未満の 月額料金(税込)
などというように、似たような系列のデータが分かれてしまっていました。
このままではtidyなデータとは言いがたいので、pivot_longer()
を使って縦長データにします💡
pacman::p_load(tidyverse)
site1[[1]] |>
pipebind::bind(x, set_colnames(x, x[1,])) |>
slice(-1) |>
set_colnames(c("キャリア", "0-1", "1-3", "3-20", "20-")) |>
pivot_longer(-1, names_to = "data_range", values_to = "price")
pivot_longer()
後は列名がそのまま行の値に変換されるため、あらかじめ正規化された列名にしておくと後が楽になります🖐️
キャリア名の表記揺らぎを直す
キャリアの名称はサイトによって違っていそう、というのは真っ先に思いつきます。
これは手作業で直すしかないんですが、ある程度はスマートに対処したいポイントです。
まず、表記のズレを探す前に一覧を確認します。
carrier_crude <- list(df_sum1, df_sum2, df_sum3) |>
map(function(x) {
x |>
pull(キャリア) |>
unique() |>
sort() |>
str_replace_all("(.*)", "")})
carrier_crude
予想通りYモバイル
やワイモバイル
といった揺らぎがありました。揺らぎリストは、重複しない差分を取り出すことであぶり出すことができます。
setdiff(carrier_crude[[2]], carrier_crude[[3]])
# [1] "Yモバイル"
setdiff(carrier_crude[[2]], carrier_crude[[1]])
# [1] "OCNモバイルONE" "povo1.0" "povo2.0" "Yモバイル"
setdiff(carrier_crude[[3]], carrier_crude[[1]])
# [1] "nuro mobile" "OCNモバイルONE" "povo1.0" "povo2.0"
# [5] "y.u.mobile"
あとは揺らぎのあるパターンをどちらか一方にそろえれば終わりです。
rename_carriers <- function(strings) {
strings |>
str_replace("Yモバイル", "ワイモバイル") |>
str_replace("OCN モバイル ONE", "OCNモバイルONE") |>
str_replace("^povo$", "povo2.0") |>
str_replace("nuro mobile", "nuroモバイル") |>
str_replace("y.u モバイル", "y.u.モバイル")
}
金額の表記揺れを直す
金額にも表記の揺らぎがあるのと、「*,***円」となっているのを数値型に直しておかないと、グラフ作成の時に困りそうです。
この「*,***円」は正規表現で抽出するのが楽です🤞
今回のケースではいろいろな表記方法がありますが、「"0-9"までの数字または","が1~5文字連続し、その直後に"円が来る"」というルールで抽出しました。
ついでに「円」と「,」もなくして、最後に数値型へ変換できるような形で関数化しておきました。
rename_pricedata <- function(strings) {
strings |>
str_extract("[0-9,]{1,5}円") |>
str_replace("円", "") |>
str_replace(",", "") |>
as.numeric()
}
集大成
ということで、これまでの処理をすべて実行したのが以下です。
site1 <- scrape_table(url1)
site2 <- scrape_table(url2)
site3 <- scrape_table(url3)
df_1 <- site1[[1]] |>
bind(x, set_colnames(x, x[1,])) |>
slice(-1)
df_1_joined <-
site1[[3]] |>
set_colnames(colnames(df_1)) |>
slice(-1) |>
bind_rows(df_1)
df_sum1 <-
df_1_joined |>
set_colnames(c("キャリア", "0-1", "1-3", "3-20", "20-")) |>
pivot_longer(-1, names_to = "data_range", values_to = "price")
df_sum2 <-
site2[[1]] |>
bind(x, set_colnames(x, x[1,])) |>
slice(-1) |>
pivot_longer(-1, names_to = "キャリア", values_to = "price") |>
set_colnames(c("data_range", "キャリア", "price"))
df_sum3 <-
site3[[1]] |>
bind(x, x[, 1:(ncol(x) - 1)]) |>
pivot_longer(-1, names_to = "キャリア", values_to = "price") |>
set_colnames(c("data_range", "キャリア", "price"))
# rename_pricedata()やrename_carriers()は前述
rename_data_range <- function(strings) {
case_when(
strings == "~1GB" ~ "0-1",
strings == "~3GB" ~ "1-3",
strings == "~7GB" ~ "3-7",
strings == "~20GB" ~ "7-20",
strings == "無制限" ~ "20-",
TRUE ~ strings
)
}
df_clean <- df_sum1 |>
mutate(resource = "site1") |>
bind_rows(df_sum2 |> mutate(resource = "site2")) |>
bind_rows(df_sum3 |> mutate(resource = "site3")) |>
mutate(price_exceedable = str_detect(price, "〜")) |>
mutate(price = rename_pricedata(price)) |>
mutate(data_range = rename_data_range(data_range)) |>
mutate(キャリア = rename_carriers(キャリア)) |>
filter(!is.na(price))
解析する
全部プロットしてみる
当初は「何分電話するか」とか「Twitterいっぱいみたら」、っていうモデル式であれこれ評価でもするかーと思ってましたが・・・
―― もう面倒くさいので全部安い順にプロットして終わりにします 。。
まずはデータ容量グループごとに、安い順に並べてプロットします。ここはfactor型のlevelsを使って並び替える作戦ですが、fct_reorder()
を使っているのがミソです。
また、x軸方向の順番もfct_relevel()
関数によりfactorの性質を利用して並び替えます。
df_clean |>
arrange(キャリア) |>
mutate(キャリア = fct_reorder(キャリア, price, min)) |>
filter(data_range != "3-20") |>
mutate(data_range = fct_relevel(data_range,
c("0-1", "1-3", "3-7", "7-20", "20-")
)) |>
ggplot(aes(x = data_range, y = price, fill = キャリア))+
geom_col(position = position_dodge())

う~ん・・・キャリアの数が多すぎてわけわかんないですね・・・🤔
数を絞るために、何か別の条件でフィルターしてみます。
3GB料金帯で安い順に見てみる
3GB帯のデータだけをフィルターしつつ、値段順にプロットしてみます。
プロットしてみると、1000円前後が相場のようなので、とりあえず税込み1100円未満のモノだけを解析対象としてみます。
フィルター後のデータをハイライトするためにgghighlightを使っています。 仕事でも大変お世話になっている神パッケージ。
df_clean |>
filter(data_range == "1-3") |>
arrange(キャリア) |>
mutate(data_range = fct_relevel(data_range,
c("0-1", "1-3", "3-7", "7-20", "20-")
)) |>
group_by(キャリア) |>
slice_min(price) |>
mutate(キャリア = fct_reorder(キャリア, price, min)) |>
ggplot(aes(x = reorder(キャリア, price), y = price, fill = キャリア))+
geom_col(position = position_dodge(preserve = "total"))+
scale_y_continuous(expand = expansion(mult = c(0, 0.1)))+
theme_bw()+
coord_flip()+
gghighlight::gghighlight(price < 1100)

私がサブ回線で使うとしたら1-5GBくらいだと思うので、1-3
あたりに注目すると1000円前後が割安な価格帯のようです。
ここからは安い順に見ていきます。
HISモバイル
新プランのデータ定額プランがめちゃくちゃ安く、データ通信に至っては580円。安すぎる。
しかし5G非対応のため見送り。多少通信速度犠牲にできるならオススメ。
リンクスメイト
知らなかったんですが、実に興味深いキャリアです😎
- 様々なゲームアプリやSNSアプリでデータフリー(に近い)が実現
- データ容量は100MB~1GB単位で選べて、めっちゃ細かい(100種類以上プランがある計算になるらしい)
- 価格設定も結構安い
- いっぱい使うとウマ娘などのゲーム内アイテムくれるらしい
私がとても気になったのは、独特なデータフリープランです。
実際には完全に通信量をノーカウントにしているわけではなく、90%カットしてくれるそうです。
・・・ということはTwitterに3GB使っても300MB・・・!!ゴクリ・・・
私のスマホ通信量のほとんどはTwitter、Spotify(この二つで3GB以上)ですので、リンクスメイトのデータフリープランに心打たれました・・・🗡️
OCNモバイルONE
3GBで月990円。やや高いが、音楽サービスのカウントフリーがある。
こちらのカウントフリーは本当にカウントゼロなので、Spotifyで音楽聴きまくる人は本当にオススメ。最初調べたときは本気でこれにしようと思ってました。5Gは対応しているし、eSIMも対応。カウントフリーも追加料金不要。
500MBプランだけ無料通話が10分あるのもアツい。
あとは好みの問題ですが、私はSpotify有料会員なのでそんなにストリーミング再生しない&podcastはカウントフリー対象外のため、あまり活用出来ない気がしてやめました。
普通にオススメ。 OCN モバイル ONE 【NTTコミュニケーションズ】|ドコモのエコノミーMVNO
nuroモバイル
3GBで月792円と、まあまあの安さ。一見カウントフリーなどもあるように見えるが、20GB契約でなければTwitter等はカウントフリーにならないので注意。地味に5Gも20GB契約しかつかない。
あんまりオススメはできない。

IIJmio
新料金プラン「データプランゼロ(eSIM)」が登場。1GB 495円と安めだが、それ以上となるとデータ容量追加をしなければならず、途端に安くなくなる。
以前からあったギガプランはもう少し魅力的。2GBで440円、4GBで660円。今色々キャンペーンがやってるので、90円追加で5分通話が追加できたりする。
カウントフリーとかまどろっこしいから気にせずに使いたい人にとっては一番お得かもしれない。番外編 povo
自分にとってはめちゃくちゃ微妙。
ギガ活(ローソンや松屋など色々なお店で500円くらい使うと数日限定で通信量が幾ばくかもらえる)を活用すればゼロ円運用可能。
しかし期限三日とかなので、常に外食・買い物をし続けてないと燃料切れをおこしやすい。
普段から外での買い物をしょっちゅうする人は本当にオススメできますが、私はそうじゃないので見送り。
番外編 楽天モバイル
というか、改悪と言われている楽天モバイル、そんなに高くないのでは・・・?
今回の料金データ比較結果から、3GBまでが980円で使える楽天モバイルは割と相場通りの価格設定であるということが分かりましたし、無料で電話し放題など、他にはないメリットがあります。
まだ移行期間もありますが、キャンペーンもあるし、あえて楽天モバイルも全然なしではないと思います。
おわりに
ということで、楽天モバイルの代わりを探してみる企画はこれにて終了です🚩
ウェブスクレイピングを取り入れてみたりしましたが、自分的にはあんまりイケてない解析だったな~と思いました🤔
ではまた~👋
コメント