Y

RをPandasに置き換える(ヒストグラム)

ビジネス活用事例で学ぶ データサイエンス入門

ビジネス活用事例で学ぶ データサイエンス入門

タイトルのとおり、ビジネス事例(アプリ開発会社の事例)を元にしてデータ分析を学ぶスタイルになっています。馴染みのある事例なのでデータ活用のイメージがしやすい内容になっています。

本書はRを使っていますが、どちらかというとPython Pandasの方が好きなので、RからPandasに置き換えてみました。

(0)R・データの準備

Rインストール

R for Mac OS X

R-3.2.3.pkgをインストールしました。

データダウンロード

ビジネス活用事例で学ぶ データサイエンス入門

上記からダウンロードしました。

(1)CSVファイル読み込み

  • DAU(1日1回以上アクセスしたユーザのデータ
  • DPU(1日1円以上課金したユーザのデータ
  • Install(ユーザごとにいつゲームを利用開始したかのデータ)

R

> dau <- read.csv("section3-dau.csv", header = T, stringsAsFactors = F)
> head(dau)
    log_date app_name user_id
1 2013-06-01  game-01     116
2 2013-06-01  game-01   13491
3 2013-06-01  game-01    7006

> dpu <- read.csv("section3-dpu.csv", header = T, stringsAsFactors = F)
> head(dpu)
    log_date app_name user_id payment
1 2013-06-01  game-01     351    1333
2 2013-06-01  game-01   12796      81
3 2013-06-01  game-01     364     571

> install <- read.csv("section3-install.csv", header = T, stringsAsFactors= F)
> head(install)
  install_date app_name user_id
1   2013-04-15  game-01       1
2   2013-04-15  game-01       2
3   2013-04-15  game-01       3

Pandas

>>> import pandas as pd
>>> dau = pd.read_csv("section3-dau.csv")
>>> dau
          log_date app_name  user_id
0       2013-06-01  game-01      116
1       2013-06-01  game-01    13491
2       2013-06-01  game-01     7006

>>> dpu = pd.read_csv("section3-dpu.csv")
>>> dpu
       log_date app_name  user_id  payment
0    2013-06-01  game-01      351     1333
1    2013-06-01  game-01    12796       81
2    2013-06-01  game-01      364      571

>>> install = pd.read_csv("section3-install.csv")
>>> install
      install_date app_name  user_id
0       2013-04-15  game-01        1
1       2013-04-15  game-01        2
2       2013-04-15  game-01        3

(2)DAUのデータにInstallデータをくっつける

R

> dau.install <- merge(dau, install, by = c("user_id", "app_name"))
> head(dau.install)
  user_id app_name   log_date install_date
1       1  game-01 2013-06-03   2013-04-15
2       1  game-01 2013-06-14   2013-04-15
3       1  game-01 2013-07-09   2013-04-15

Pandas

>>> dauinstall = pd.merge(dau, install, how='left', on=['user_id', 'app_name'])
>>> dauinstall
          log_date app_name  user_id install_date
0       2013-06-01  game-01      116   2013-04-17
1       2013-06-01  game-01    13491   2013-06-01
2       2013-06-01  game-01     7006   2013-05-03

(3)dau.installに、さらにDPUデータをくっつける

R

> dau.install.payment <- merge(dau.install, dpu, by = c("log_date", "app_name", "user_id"), all.x = T)
> head(dau.install.payment)
    log_date app_name user_id install_date payment
1 2013-06-01  game-01       1   2013-04-15      NA
2 2013-06-01  game-01       3   2013-04-15      NA
3 2013-06-01  game-01       6   2013-04-15      NA

Pandas

>>> dauinstallpayment = pd.merge(dauinstall, dpu, how = 'left', on = ['log_date', 'app_name', 'user_id'])
>>> dauinstallpayment
          log_date app_nam  user_id install_date  payment
0       2013-06-01  game-01      116   2013-04-17      NaN
1       2013-06-01  game-01    13491   2013-06-01      NaN
2       2013-06-01  game-01     7006   2013-05-03      NaN

(4)payment=NAのデータを除外する

R

> head(na.omit(dau.install.payment))
      log_date app_name user_id install_date payment
7   2013-06-01  game-01      19   2013-04-15     162
81  2013-06-01  game-01     351   2013-04-18    1333
84  2013-06-01  game-01     364   2013-04-18     571

Pandas

>>> dauinstallpayment.dropna(subset=['payment'])
          log_date app_name  user_id install_date  payment
12      2013-06-01  game-01       19   2013-04-15      162
84      2013-06-01  game-01      351   2013-04-18     1333
87      2013-06-01  game-01      364   2013-04-18      571

(5)payment=NAのデータに課金額0を設定する

R

> dau.install.payment$payment[is.na(dau.install.payment$payment)] <- 0
> head(dau.install.payment)
    log_date app_name user_id install_date payment
1 2013-06-01  game-01       1   2013-04-15       0
2 2013-06-01  game-01       3   2013-04-15       0
3 2013-06-01  game-01       6   2013-04-15       0

Pandas

>>> dauinstallpayment.fillna(0)
          log_date app_name  user_id install_date  payment
0       2013-06-01  game-01      116   2013-04-17        0
1       2013-06-01  game-01    13491   2013-06-01        0
2       2013-06-01  game-01     7006   2013-05-03        0

>>> dauinstallpayment.fillna(0, inplace=True)
>>> dauinstallpayment
          log_date app_name  user_id install_date  payment
0       2013-06-01  game-01      116   2013-04-17        0
1       2013-06-01  game-01    13491   2013-06-01        0
2       2013-06-01  game-01     7006   2013-05-03        0

(6)月次で集計する

R

> dau.install.payment$log_month <- substr(dau.install.payment$log_date, 1, 7)
> dau.install.payment$install_month <- substr(dau.install.payment$install_date, 1, 7)
> library("plyr")
> mau.payment <- ddply(dau.install.payment, .(log_month, user_id, install_month), summarize, payment = sum(payment))
> head(mau.payment)
  log_month user_id install_month payment
1   2013-06       1       2013-04       0
2   2013-06       2       2013-04       0
3   2013-06       3       2013-04   14994

Pandas

dauinstallpayment['log_month'] = dauinstallpayment['log_date'].str[0:7]
dauinstallpayment['install_month'] = dauinstallpayment['install_date'].str[0:7]

>>> maupayment = dauinstallpayment.groupby(['user_id','log_month', 'install_month'])['payment'].sum() 
>>> maupayment
user_id  log_month  install_month
1        2013-06    2013-04              0
         2013-07    2013-04              0
2        2013-06    2013-04              0
3        2013-06    2013-04          14994

(7)新規ユーザか既存ユーザかの区分を追加する

R

> mau.payment$user.type <-ifelse(mau.payment$install_month == mau.payment$log_month, "install", "existing")
> mau.payment.summary <- ddply(mau.payment, .(log_month, user.type), summarise, total.payment = sum(payment))
> head(mau.payment)
  log_month user_id install_month payment user.type
1   2013-06       1       2013-04       0  existing
2   2013-06       2       2013-04       0  existing
3   2013-06       3       2013-04   14994  existing
4   2013-06       4       2013-04       0  existing
5   2013-06       6       2013-04       0  existing
6   2013-06       7       2013-04       0  existing
> head(mau.payment.summary)
  log_month user.type total.payment
1   2013-06  existing        177886
2   2013-06   install         49837
3   2013-07  existing        177886
4   2013-07   install         29199

Pandas

>>> dauinstallpayment['user_type'] = np.where(dauinstallpayment['log_month'] == dauinstallpayment['install_month'], 'install', 'existing')
>>> dauinstallpayment
          log_date app_name  user_id install_date  payment log_month install_month user_type
0       2013-06-01  game-01      116   2013-04-17        0   2013-06       2013-04  existing
1       2013-06-01  game-01    13491   2013-06-01        0   2013-06       2013-06   install
2       2013-06-01  game-01     7006   2013-05-03        0   2013-06       2013-05  existing

(8)グラフによりデータを可視化する(新規、既存比較)

R

ggplot(mau.payment.summary, aes(x = log_month, y = total.payment, fill = user.type)) + geom_bar(stat="identity") + scale_y_continuous(label = comma)

スクリーンショット 2016-02-27 15.09.32.png

Pandas

TODO..

(9)グラフによりデータを可視化する(金額別売上比較)

R

ggplot(mau.payment[mau.payment$payment > 0 & mau.payment$user.type == "install", ], aes(x = payment, fill = log_month)) + geom_histogram(position = "dodge", binwidth = 2000)

スクリーンショット 2016-02-27 15.10.30.png

Pandas

TODO..

補足 Pandasを0.17にした

Equivalent of R/ifelse in Python/Pandas? Compare string columns?

conda update anaconda
conda update conda
conda update pandas

numpy:  1.9.2-py34_0      --> 1.10.4-py34_0     
pandas: 0.16.2-np19py34_0 --> 0.17.1-np110py34_0
pytz:   2015.4-py34_0     --> 2015.7-py34_0     
six:    1.9.0-py34_0      --> 1.10.0-py34_0 

まとめ

Rの良いところの感想は以下です。しばらくRとPandasを併用してみてPro/Conを考えてみます。いずれにしても両方知っておくと、情報量を増やすことができるので良いと思いました。

  • Rのインストールが簡単
  • グラフがmatplotlibより(少し)オシャレ

参考

ビジネス活用事例で学ぶ データサイエンス入門

RjpWiki

R for Mac OS X

Merge, join, and concatenate

Python pandas 図でみる データ連結 / 結合処理

Equivalent of transform in R/ddply in Python/pandas?

pandasでよく使う文法まとめ

Python pandas strアクセサによる文字列処理

pandasメモ

Equivalent of R/ifelse in Python/Pandas? Compare string columns?

TipMe

TipMe with IndieSquare

Tower of Babelを使ってES2015を学ぶ

ES6を学べるチュートリアル

github yosuke-furukawa/tower-of-babel

スクリーンショット 2016-02-26 21.07.07.png

多少カンニングしましたが.. 一応クリアしました。ES2015の新機能を学べます。継承、Block Scope、モジュールなど、これまでよりスッキリ実装できそうです。

ES6の全ての新機能を網羅しているわけではないのでfolkして項目を追加しても良さそう(問題作るのは大変そうですが...)

ES5についても曖昧(prototypeとか)なので、ES5版も作って欲しいです..

ES2015を使いたくなってきました!

参考

github yosuke-furukawa/tower-of-babel

ES6 を学べる tower-of-babel を作りました。 (workshopper の作り方)

Class構文について

JavaScriptでforEach, filter, map, reduceとか

ES2015(ES6)な時代だからこそ、ES5を改めて調べたJavaScript初級者のメモ

ECMAScript 6 compatibility table

React.js + Babel + Browserify + gulp の環境を作ってみた

TipMe

TipMe with IndieSquare

counterwallet.ioが改竄された時に僕のビットコインは安全か

ウィルス感染でWebサービスが20日間ダウン

ウィルス感染でWebサービスが20日間ダウン。本当にごめんなさい

サーバーを乗っ取られて信用を落とした顛末が書かれてある記事。他人事ではない。

counterwallet.ioは秘密鍵を保持しないから安全!?

counterwallet.ioは秘密鍵を保持しないから安全と言われています。ただしサーバーがハッキングされてAPIが改竄されていた場合に以下のような危険があります。

送信先が改変されたtxにサインしてしまう危険性

XCP送信例。トランザクション作成

  • 送信元: 12sWrxRY7E7Nhmuyjbz4TtGE9jRewGqEZD
  • 送信先: 1965areciqapsuL2hsia2yKkRLfAsH1smG
  • 送金額: 0.0005XCP
curl http://xxx:xxx/api/ --user xxx:xxx -H 'Content-Type: application/json; charset=UTF-8' -H 'Accept: application/json, text/javascript' --data-binary '{"jsonrpc":"2.0", "id":0, "method":"create_send",   "params":{"source":"12sWrxRY7E7Nhmuyjbz4TtGE9jRewGqEZD", "destination":"1965areciqapsuL2hsia2yKkRLfAsH1smG", "quantity":50000, "asset":"XCP"}}'

レスポンス

未サイントランザクションが返ります。

{"id": 0, "result": "01000000038273bcfe9961f0eb03fa90bdb91af8d1a96a18cdfcf7e362e6260dff7ebf9851000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88acffffffff7dd4cdce57f4cd83137e669cd4c64ae749433b889df781fb4fc30b6f219ae61b000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88acffffffffccc2c1ddf717c52276461ad031d9471c8f459a5ba3865cb381ecb5a914fd6cfe000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88acffffffff0336150000000000001976a91458b6e991b45487df810f4d96d5315da739637f1788ac00000000000000001e6a1c176441d9b0d5e714def4becc729931ec91004dfaf58b4d18c6757f1c73310000000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88ac00000000", "jsonrpc": "2.0"}

送信先の改竄のリスクがある

例えばPHPAPIを実装していた場合なら、送信先を こそっと 変更することは簡単です。レスポンスを見ただけでは正しいかは分かりません。

counterwallet.ioは未サイントランザクションのチェックがある

さすがにマズイのでcounterwallet.ioは、サイン前に未サイントランザクションのチェックを行っています。以下に必要そうなところだけ抜き出しました。

CWBitcore.checkTransactionDest = function(txHex, source, dest) { 
  var tx = CWBitcore.parseRawTransaction(txHex);    
  for (var i=0; i<tx.outs.length; i++) {
      var addresses = CWBitcore.extractAddressFromTxOut(tx.outs[i]).split(',');
      var containsSource = _.intersection(addresses, source).length > 0;
      var containsDest = _.intersection(addresses, dest).length > 0;
      if ((containsSource == false && containsDest == false) && 
           tx.outs[i].getScript().classify() != bitcore.Script.TX_RETURN &&
           tx.outs[i].getScript().classify() != bitcore.Script.TX_UNKNOWN) {
        return false;
      }
  return true;
}

CWBitcore.extractAddressFromTxOut = function(output) {
  switch (output.script.classify()) {
    case bitcore.Script.types.PUBKEY_OUT:
      return output.script.toAddress(NETWORK).toString();

    case bitcore.Script.types.PUBKEYHASH_OUT:
      return output.script.toAddress(NETWORK).toString();

    case bitcore.Script.types.SCRIPTHASH_OUT:
      return output.script.toAddress(NETWORK).toString();

    case bitcore.Script.types.MULTISIG_OUT:
      var addresses = CWBitcore.extractMultiSigAddressesFromScript(output.script);
      return addresses.join(",");

    case bitcore.Script.types.DATA_OUT:
      return "";

    default:
      throw new Error("Unknown type [" + output.script.classify() + "]");
  }
}

未サイントランザクションCheck簡易版

説明を簡単にするために簡易版のスクリプトを用意しました。

bower install

bower install bitcore-lib
bower install bitcore-mnemonic

html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <script src="bower_components/bitcore-lib/bitcore-lib.min.js"></script>
  <script src="bower_components/bitcore-mnemonic/bitcore-mnemonic.min.js"></script>
</head>
<body>
  <script type="text/javascript">
    var bitcore = require('bitcore-lib');
    var Mnemonic = require('bitcore-mnemonic');

    var raw = new bitcore.deps.Buffer('01000000038273bcfe9961f0eb03fa90bdb91af8d1a96a18cdfcf7e362e6260dff7ebf9851000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88acffffffff7dd4cdce57f4cd83137e669cd4c64ae749433b889df781fb4fc30b6f219ae61b000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88acffffffffccc2c1ddf717c52276461ad031d9471c8f459a5ba3865cb381ecb5a914fd6cfe000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88acffffffff0336150000000000001976a91458b6e991b45487df810f4d96d5315da739637f1788ac00000000000000001e6a1c176441d9b0d5e714def4becc729931ec91004dfaf58b4d18c6757f1c73310000000000001976a9141485d9d03b41aaa9dca7d70d7f63ff4a0826100e88ac00000000', 'hex');
    var tx = new bitcore.Transaction(raw);

    for (var i = 0; i < tx.outputs.length; i++) {
      var type = tx.outputs[i].script.classify();
      switch (type) {
        case bitcore.Script.types.PUBKEY_OUT:
        case bitcore.Script.types.PUBKEYHASH_OUT:
        case bitcore.Script.types.SCRIPTHASH_OUT:
          console.log(tx.outputs[i].script.toAddress(bitcore.Networks.livenet).toString());
          break;
          
        case bitcore.Script.types.MULTISIG_OUT:
        case bitcore.Script.types.DATA_OUT:
        default:
          console.log('Unknown type [' + type + ']');
      }
    }
  </script>
</body>
</html>

コンソールログ

index.html:28 1965areciqapsuL2hsia2yKkRLfAsH1smG
index.html:34 Unknown type [Data push]
index.html:28 12sWrxRY7E7Nhmuyjbz4TtGE9jRewGqEZD

Blockchain.info

3つのスクリプトに含まれる2つのアドレスが、コンソールに出力されたアドレスと一致しています。この一致を比較することで意図した送金を確認できます。

スクリーンショット 2016-02-23 17.02.52.png

Data pushスクリプトの中身は見なくていいの?

BitcoinとCounterpartyの送信トランザクションの比較 に書きましたが、Data pushスクリプトには送金元、送金先の情報は含めないので、悪意あるユーザーに送金されることはありえません。

ただし、愉快犯的なクラッカーにより不正な値のトランザクションが送信される可能性があります。(例えば、勝手にアセットにロックをかけたり、配当もできる..?)

チェックスクリプト自体を改変された場合はどうなるのか

Webアプリの場合はチェックスクリプト自体を改変・無効に変更することができますね.. コード改竄監視は必要になりそうです。気になるのでcounterwallet.ioコミュニティに質問投げてみます。

クライアントがモバイルアプリの場合は、チェックスクリプトの改竄リスクは低いでしょう。

まとめ

サーバのセキュリティを強化しないといけない.. と思いました。どのプロダクトが良いのか調べてみます。

参考

Underscore.js

Problem with Buffer when trying to add a string to a script

Bitcoins the hard way: Using the raw Bitcoin protocol

BitcoinとCounterpartyの送信トランザクションの比較

TipMe

TipMe with IndieSquare

実質実効為替レートと失業率の相関を調べる

円安ってそんなに大事なのか

どちらかというと円安の方が景気に良いって言われるけど、ほんとにそうなのか気になったので失業率との相関を調べてみました。

失業率のデータ取得

就業者データと同様に総務省統計局からExcelデータを取得してpandasで読み込みます。

データ取得

import pandas as pd
import urllib.request as urllib2
link = 'http://www.stat.go.jp/data/roudou/longtime/zuhyou/lt01-a70.xls'
socket = urllib2.urlopen(link)
xls = pd.ExcelFile(socket)
df = xls.parse(xls.sheet_names[0], skiprows=10, header=0, skip_footer=3)
df.columns = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N']
df = pd.DataFrame({'Total': df['J']})

日付設定

rng = pd.date_range('1/1/1968', periods=576, freq='M')
df['Date'] = rng

グラフ化

import matplotlib.pyplot as plt
import numpy as np
plt.figure(figsize=(10,5))
plt.grid()
x = np.array(df['Date'])
y = np.array(df['Total'])
plt.plot(x, y)
plt.show()

スクリーンショット 2016-02-22 23.36.23.png

失業率が1.0%の時代がある!! 驚き!

実質実効為替レートとは

次は失業率のグラフに実質実効為替レートを重ねます。

「実効為替レート(名目・実質)」の解説

実効為替レートは、特定の2通貨間の為替レートをみているだけでは捉えられない、相対的な通貨の実力を測るための総合的な指標です。具体的には、対象となる全ての通貨と日本円との間の2通貨間為替レートを、貿易額等で計った相対的な重要度でウエイト付けして集計・算出します。

1ドルX円とかでなくて、80とか100とかの数値で表したレート。レートが高い方が円高。

日銀から実質実効為替レートデータ取得

日銀の時系列統計データ検索サイトから1968年〜2015年の実質実効為替レートデータを取得してください。(CSVファイル、ヘッダなし)

以下メニューをたどるとダウンロードできます。(だいぶ面倒です...)

各種マーケット関連統計 (ST) > 外国為替市場 > 実効為替レート(名目・実質)

CSVファイル読み込み

df_rate = pd.read_csv("/tmp/nme_R031.633.20160222234838.01.csv", header=None)
df_rate.columns = ['A','B']
df['Rate'] = df_rate['B']
df
     Total       Date   Rate
0      1.3 1968-01-31    NaN
1      1.3 1968-02-29    NaN
2      1.2 1968-03-31    NaN
..     ...        ...    ...
573    3.1 2015-10-31  71.64
574    3.3 2015-11-30  70.66
575    3.3 2015-12-31  71.57

実質実効為替レートと失業率の相関をグラフ化

x = np.array(df['Date'])
y_total = np.array(df['Total'])
y_rate = np.array(df['Rate'])

fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(111)
plt.plot(x, y_total, color='b')
plt.ylabel("Unemployment Rate",fontsize=14,color='b')

ax2 = ax1.twinx()
plt.plot(x, y_rate, color='r')
plt.ylim(ymin=60)
plt.ylim(ymax=140)
plt.ylabel("Exchange Rate",fontsize=14,color='r')

plt.grid(True)
plt.show()

スクリーンショット 2016-02-23 00.56.41.png

まとめ

2000年以降は実質実効為替レートと失業率の相関は高そうです。バブルの頃は円高でも失業率に影響しなかったのでしょうか.. 現在の失業率(3%台半)は完全雇用の状態らしいです。これ以上改善されないということでしょうか今後の推移が気になります。

なるほど

数字・データ・統計的に正しい日本の針路 (講談社+α新書)

為替は2つの通貨の交換比率なので、どちらが相対的に多いかどうかできまる。円安は円がドルに対して相対的に多いということなので、日本が金融緩和すればそうなる。その一方、モノに対しても円が多い状態になるので、モノの価格は上がる。つまりデフレになりにくい。

参考

「実効為替レート(名目・実質)」の解説

時系列統計データ検索サイト

オイルショック

完全雇用

TipMe

TipMe with IndieSquare

総務省 労働力調査 長期時系列データ(Excel)をpandasで読み込む

就業者数の推移が気になった

リーマン・ショック民主党政権、第2次安倍内閣。ここ10年ぐらいの就業者数の推移が気になったのでグラフ化しました。

労働力データ取得

import pandas as pd
import urllib.request as urllib2
link = 'http://www.stat.go.jp/data/roudou/longtime/zuhyou/lt01-a30.xls'
socket = urllib2.urlopen(link)
xls = pd.ExcelFile(socket)
df = xls.parse(xls.sheet_names[0], skiprows=10, header=0, skip_footer=3)
df.columns = ['A','B','C','D','E','F','G','H','I','J','K','L','N']
df = pd.DataFrame({'Total': df['E']})

日付はdate_rangeで生成

rng = pd.date_range('1/1/1953', periods=756, freq='M')
df['Date'] = rng

グラフ化

import matplotlib.pyplot as plt
import numpy as np
x = np.array(df['Date'])
y = np.array(df['Total'])
plt.plot(x, y)
plt.show()

スクリーンショット 2016-02-22 01.20.19.png

2007年から2015年までの期間

x = np.array(df['Date'][648:])
y = np.array(df['Total'][648:])
plt.plot(x, y)
plt.show()

スクリーンショット 2016-02-22 01.41.08.png

まとめ

安倍内閣の間に雇用が150万人増えていることが分かります。参考数値として鹿児島県の人口は約160万人。ほぼ同数の雇用が増えたということですね。政府は一億総活躍社会の実現を目指していますが今後の就業者数の推移が楽しみです。

参考

労働力調査 長期時系列データ

データを取り込む・格納するための方法を理解する

Time Series / Date functionality

Python pandas で日時関連のデータ操作をカンタンに

鹿児島県毎月推計人口(平成28年1月1日現在)

TipMe

TipMe with IndieSquare

Parse.comの代替としてAmazonSNSを使う

Parse.com終わりますね..

Top 5 Parse Alternatives

あちこちで悲鳴が聞こえます... Facebookに買収されたから安心していたんですけどね..

AmazonSNSにします

Amazon SNSでモバイルPush/HTTP Push通知

いろいろ比較しましたが乗り換え先はAmazonSNSに決めました。決め手はAmazonだから(..過去から学んでない)、価格、API、Webコンソールが便利。などです。

クラウドのサービスはGoogle Cloud Platformをメインに使っているのでFirebaseを検討しましたがPush通知機能が無いので諦めました。(今後に期待)

fastlane pemは便利だった

fastlane/pem

サンプルアプリでAmazonSNSの動作を確認しようとしてPush通知の設定を忘れていました。調べてみると最近はfastlane pemなるものがあり便利でした。(fastlaneは言語ごとのスクリーンショットを保存できたり他にも便利機能ありますよね)

インストール

$sudo gem install pem

証明書作成

$pem --development 

AmazonSNSの設定

(1)アプリケーション作成ボタンクリック

スクリーンショット 2016-02-13 22.33.05.png

(2)アプリケーション設定

fastlane pemで作成した.p12ファイルを選択した後に、Load Credentials from FileボタンをクリックするとCertificateとPrivate Key項目は自動で設定されます。

スクリーンショット 2016-02-13 22.01.16.png

(3)エンドポイント作成ボタンクリック

スクリーンショット 2016-02-13 22.01.49.png

(4)エンドポイント設定

スクリーンショット 2016-02-13 22.02.35.png

(5)Publishボタンクリック

スクリーンショット 2016-02-13 22.06.51.png

(6)Publish設定

スクリーンショット 2016-02-13 22.08.19.png

(7)Push通知受信

スクリーンショット 2016-02-13 22.36.39.png

無事通知来ました

AmazonSNS APIを使う

API経由でエンドポイント作成とパブリッシュを行います。

composer install

PHPのライブラリがあるので簡単です

"aws/aws-sdk-php": "3.*"

エンドポイント作成

Laravelのコマンドに実装しました

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Aws\Sns\SnsClient;

class PublishPushNotification extends Command
{
    protected $signature = 'aws-sns:publish';
    protected $description = 'This command will publish a notification';

    public function handle() {
        $sns = new SnsClient([
            'credentials' => [
                'key'    => 'xxxx',
                'secret' => 'xxxx'
            ],
            'region' => 'us-west-2',
            'version' => '2010-03-31'
        ]);

        // Create Endpoint
        $arn = 'arn:aws:sns:xxxx';
        $token = 'xxxx';
        $options = array(
                    'PlatformApplicationArn' => $arn,
                    'Token' =>$token,
                    'CustomUserData' => '',
        );

        try {
            $result = $sns->createPlatformEndpoint($options);
            echo $result['EndpointArn'];
        } catch (Exception $e) {
            echo $e->getMessage(); // TODO logging
        }
}

パブリッシュ

// Publish
$arn = 'arn:aws:sns:xxxx';
$options = array(
  'Message'   => 'Hello!',
  'TargetArn' => $arn
);

try {
  $result = $sns->publish($options);
  echo $result['MessageId'];
} catch (Exception $e) {
  echo $e->getMessage(); // TODO logging
}

まとめ

以下がTODOです。今度やります。

  • 言語毎のメッセージ分けを簡単にできないかな
  • IAMで権限を絞る

あとiOS9からプッシュ通知に変更があったの知らなかったのでメモしておきます。

WWDC 2015 – Big changes to Apple push notifications

  • Push notification actions
  • APNS token size
  • Providers API

参考

fastlane/pem

Amazon SNSでモバイルPush/HTTP Push通知

Top 5 Parse Alternatives

WWDC 2015 – Big changes to Apple push notifications

Amazon Simple Notification Service

AWS SDK Installation

Class SnsClient

AWS SDK for PHPのVersion 3に更新して、EC2自動起動処理を書き換えてみた

気軽なSNS Mobile Push の話

Amazon SNS 料金

TipMe

TipMe with IndieSquare

URLスキームはダイアログが出るからUniversal Linksを試してみた

URLスキームはiOS9からダイアログが出るようになった

いまさらネタですが... URLスキームを使ってアプリを開く、またはアプリダウンロードを促す画面に遷移する。これをやるためにアレコレ実装していましたが、iOS9からできなくなりました。

これまではなんとか実装していた

iOSはURLスキーマ起動を試みて、500ms待ってもダメだったらストアに遷移するって実装をしてましたが、iOS9から確認ダイアログが表示される仕様になりました。

if (userAgent.search(/iphone|ipad|ipod/) > -1) {
  launch_frame.location.href = IOS_SCHEME + '://';
  setTimeout(function() {
      location.href = IOS_STORE;
  }, 500);
}

アプリインストール済みの時

スクリーンショット 2016-02-12 14.31.25.png

アプリをインストールしていない時

スクリーンショット 2016-02-12 14.32.23.png

ちなみにAndroidは問題ない

Androidは以下の設定で実現できます。(アプリが未インストールならストアが開く。簡単!)

if (userAgent.search(/android/) > -1) {
  document.location = 'intent://#Intent;scheme=' + ANDROID_SCHEME + ';package=' + ANDROID_PACKAGE + ';end';
}

Universal Linksなら解決できる?

iOS9の新機能Universal Linksなら解決できるかもしれないと思い、まずUniversal Linksを体験しました。

51 iOS 9 Apps That Support Universal Links (updated Dec 19th)

(1)Citymapperのサンプルリンクをクリック

IMG_2019.JPG

(2)アプリ未インストール時

Safariで地図が表示されます。

IMG_2020.JPG

(3)アプリインストール時

アプリが開き周辺の情報が表示されます。

IMG_2009.JPG

シームレスですね。なんとなくUniversal Linkのことが分かりました。

自分のアプリで試してみます

(1)設定(アプリ、サーバ側)

URLスキーム・独自ディープリンク実装に代わる、Universal Links(iOS 9で導入)でより良いUXを実現

新規プロジェクトを作成して、上記サイトの

    1. iOSアプリでCapabilitiesの設定
    1. apple-app-site-associationというファイルをWebサイトに配置、署名

を行えばOKです。(詳しい説明は省略します)

(2)実行

iPhoneのメモ帳にURLを書きます。

https://sample.com/hoge

URLをタップした時に、(1)で作成したアプリをインストール済みの場合はアプリが開き、未インストール時は https://sample.com/hoge のWebページがSafariで表示されますね。

まとめ

パラメータの受け取り、pathsの設定など本格的に利用するにはもっと詳しく調べる必要がありそうです。

参考

URLスキームを利用して、アプリが入ってたらアプリ起動、入ってなかったらストアへ!を実現。

URLスキーム・独自ディープリンク実装に代わる、Universal Links(iOS 9で導入)でより良いUXを実現

iOS9でカスタムURLスキームの遷移に失敗するときの注意点

[iOS9] カスタムURLスキームで起動に失敗する(呼び出し元)

Universal Links に対応するときに考慮したいこと

URLSchemeでアプリの有無によって挙動を変える(iOS9対応版)

51 iOS 9 Apps That Support Universal Links (updated Dec 19th)

Universal Links のサーバ側の対応をやってみた

Breaking down iOS 9 Universal Links

Universal Link Validator

iOS 9 Universal Links (apple-app-site-association) blues

TipMe

TipMe with IndieSquare