dplyrでうっかり八兵衛にならないように !! を意識する

うっかりやっちまったのでメモっておく。 まず適当な data.frame() を定義する。

> df <- data.frame(x=sample(1:2, 10, replace=TRUE), y=sample(c("a", "b"), 10, replace = TRUE))
> df
   x y
1  2 a
2  2 b
3  2 b
4  2 b
5  1 b
6  1 a
7  2 b
8  1 b
9  1 b
10 2 b

で、これを適当にFilteringしたいとするとこう書く。

> dplyr::filter(df, x == 1, y == "b")
  x y
1 1 b
2 1 b
3 1 b

そして「よくよく考えると x を変数にしておいたほうが楽そうだぞ〜」ということで以下のように書くと うっかり八兵衛になる (答えが狂う)。

> x <- 1
> dplyr::filter(df, x == x, y == "b")
  x y
1 2 b
2 2 b
3 2 b
4 1 b
5 2 b
6 1 b
7 1 b
8 2 b

正しくは以下のように rlang::!! をちゃんとかませておかないといけない。

> dplyr::filter(df, x == !!x, y == "b")
  x y
1 1 b
2 1 b
3 1 b

rlang は偉大なり、うっかりうっかり。

set/list/dictあたりは内部で __str__ではなく __repr__ を呼んでいるっぽい

掲題の件、そういうことです。 __str__() だけ定義しておくと

class Hoge():
    def __init__(self, x):
        self._x = x
    def __str__(self):
        return "Hoge({})".format(self._x)

ちゃんと出てくれない。

> x = {Hoge(1), Hoge(2)}
> print(x)
{<__main__.Hoge object at 0x109fb7880>, <__main__.Hoge object at 0x109fb78e0>}

一方、 __repr__() を定義しておくと

class Hoge():
    def __init__(self, x):
        self._x = x
    def __str__(self):
        return self.__repr__()
    def __repr__(self):
        return "Hoge({})".format(self._x)

ちゃんと出る。

> x = {Hoge(1), Hoge(2)}
> print(x)
{Hoge(1), Hoge(2)}

SBI証券の手数料プランはスタンダード/アクティブプランのどちらを選ぶべきか?

俺はSBI証券を使っているのだが、その手数料プラン(アクティブ or スタンダード)をどっちにするのかあまり真面目に考えてこなかったので、真面目に考えたい。

俺の俺による俺のためのデータサイエンスだ。

さて、国内株式の手数料を教えてくださいのページにあるように国内株式の手数料は下記のように与えられる。 f:id:teramonagi:20200325131548p:plain

この情報を元に

  • 一日の取引回数(The number of trading)
  • 一回の取引金額(Unit trading price)
  • 一回の取引金額のばらつき(これは一回の取引金額の50% ~ 150%になるように一様に振っている)

をある程度の幅で振ってシミュレーションさせた結果が以下になる。

図1 f:id:teramonagi:20200327092606p:plain

この表は

  • 横軸:一日の取引回数(The number of trading)
  • 縦軸:一回の取引金額(Unit trading price)

で、図中に出てくる数値はそれぞれ

  • a: アクティブプラン
  • s: スタンダードプラン

の ”一取引あたりの手数料(平均値)” の数値を表してて、水色背景だとアクティブプランを選ぶのが有利(手数料安い)、赤色背景だとスタンダードプラン有利という見方をする。

これを見るとざっくりとした傾向として

  • 一回の取引金額(Unit trading price)が少ないときはアクティブプラン
  • 一回の取引金額(Unit trading price)が大きいときはスタンダードプラン

と選べば良さそう。あとは意外と甲乙つけ難しというか取引回数に依存しそうだなということがわかった。 ちなみに "一回の取引金額のばらつき"を0にしても結論は変わらずな図になる。

おまけとして、一回の取引金額を固定して、一取引あたりの手数料分布の一日の取引回数依存性を見てみると以下のようになる。 この取引金額のレンジだと取引回数を増やせば増やすほどアクティブプランがお得になってるのがわかる。

図2:一回の取引金額が10万円の時の一取引あたりの手数料分布(横軸は一日の取引回数) f:id:teramonagi:20200327093723p:plain

図3:一回の取引金額が100万円の時の一取引あたりの手数料分布(横軸は一日の取引回数) f:id:teramonagi:20200327093720p:plain

結論

「月にO(10万円)、ETFで積み増しする」のをメインに、動かしても一日1~2百万レベルだとするとアクティブプランでいいんだろうな~。 ちなみに米国株でも似たような計算をしたが、明らかに手数料が高くてアレな気持ちになった。

スタンダードプランだと手数料に応じたポイント還元もあるようだが、還元率が1.1%なのであまり魅力的には見えない。

Reproducible Code

library("dplyr")
library("tidyr")
library("ggplot2")

fee_us <- function(x, usdjpy = 110){
  min(usdjpy * 20, 0.45 * 0.01 * x)
}
fee_active <- function(x){
  x <- x/10^4
  if(x <= 50){
    0
  } else if(x > 50 & x <= 100){
    762
  } else{
    762 + 400*ceiling((x - 100) / 100)
  }
}

fee_standard <- function(x){
  x <- x/10^4
  if(x <= 5){
    50
  } else if(x >   5 & x <=  10){
    90
  } else if(x >  10 & x <=  20){
    105
  } else if(x >  20 & x <=  50){
    250
  } else if(x >  50 & x <= 100){
    487
  } else if(x > 100 & x <= 150){
    582
  } else if(x > 150 & x <= 3000){
    921
  } else{
    973
  }
}

simulate_one <- function(size_mc, size_trading, mean_price, usdjpy, noize=c(min=0.5, max=1.5)){
  x <- list()
  for(i in seq_len(size_mc)){
    price <- rep(mean_price, size_trading)
    if(!is.null(noize)){
      price <- price*runif(size_trading, min=noize["min"], max=noize["max"])
    }
    active <- fee_active(sum(price))
    standard <- sum(purrr::map_dbl(price, fee_standard))
    us <- sum(purrr::map_dbl(price, ~ fee_us(.x, usdjpy)))
    total_cost <- c(active, standard)
    x[[i]] <- list(
      type = c("active", "standard"),
      total_cost = total_cost,
      cost_per_trade = total_cost/size_trading,
      total_amount = rep(sum(price), 2),
      amount_per_trade <- rep(mean(price), 2)
    )
  }
  cbind(size_mc, size_trading, mean_price, bind_rows(x))
}

parameter <- expand.grid(
  size_trading = c(1, 2, 3, 5, 10, 20, 50, 100), 
  mean_price   = c(1, 5, 10, 15, 20, 30, 50, 100, 200, 300, 500, 1000, 3500) * 10^4
)
size_mc <- 10^2
usdjpy <- 110
df <- purrr::pmap_dfr(parameter, function(...) {
    x <- tibble(...)
    #simulate_one(size_mc, x$size_trading, x$mean_price, usdjpy, noize=c(min=1, max=1))
    simulate_one(size_mc, x$size_trading, x$mean_price, usdjpy, noize=c(min=0.5, max=1.5))
}
)
dfs <- df %>% group_by(size_trading, mean_price, type) %>% 
  summarize(
    total_cost_mean     = mean(total_cost), 
    total_cost_sd       = sd(total_cost), 
    cost_per_trade_mean = mean(cost_per_trade), 
    cost_per_trade_sd   = sd(cost_per_trade)
  ) %>% 
  select(size_trading, mean_price, type, cost_per_trade_mean) %>%
  tidyr::pivot_wider(names_from = type, values_from = cost_per_trade_mean) %>%
  mutate(
    active_is_better = active < standard, 
    text = paste0("a: ", round(active), "\ns: ", round(standard)))
# 図1
ggplot(dfs, aes(factor(size_trading), factor(mean_price), fill=active_is_better)) +
  geom_tile(color = "gray") + 
  geom_text(aes(label = text), lineheight = 0.8) + 
  labs(y="Unit trading price", x = "The number of trading")
# 図2, 3
ggplot(filter(df, mean_price ==  10*10^4, size_trading <= 100), aes(x=factor(size_trading), y=cost_per_trade, fill=type)) + geom_violin(scale = "width") 
ggplot(filter(df, mean_price == 100*10^4, size_trading <= 100), aes(x=factor(size_trading), y=cost_per_trade, fill=type)) + geom_violin(scale = "width") 

データフレーム(data.frame)を一行ずつ処理したい

ナウく書きたい。

rmap でもいいけど、以下のように purrr::pmap() が良さげ。

> head(iris, 3) %>%
+   purrr::pmap_dfr(function(...) {
+     df_row <- tibble(...)
+     print(df_row)
+     df_row
+   })
# A tibble: 1 x 5
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
         <dbl>       <dbl>        <dbl>       <dbl> <fct>  
1          5.1         3.5          1.4         0.2 setosa 
# A tibble: 1 x 5
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
         <dbl>       <dbl>        <dbl>       <dbl> <fct>  
1          4.9           3          1.4         0.2 setosa 
# A tibble: 1 x 5
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
         <dbl>       <dbl>        <dbl>       <dbl> <fct>  
1          4.7         3.2          1.3         0.2 setosa 
# A tibble: 3 x 5
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
         <dbl>       <dbl>        <dbl>       <dbl> <fct>  
1          5.1         3.5          1.4         0.2 setosa 
2          4.9         3            1.4         0.2 setosa 
3          4.7         3.2          1.3         0.2 setosa 

参考

乱れた列を揃えたい(主にSQL用途)

create table hoge (
  hoge string
  hageeeeeeeeeeee int
  moge string
)

create table hoge (
  hoge                     string
  hageeeeeeeeeeee int
  moge                    string
)

のように揃えたいときは、↑を一旦適当なファイルに突っ込んでおいて、column するといい

$ column -t -s $' ' <filename>

ggplot2の軸とかに数式を使いたい

expression 使えばよろし。

library("mmetrics")
df <- gsummarise(iris, Species, metrics = define(y=sum(Petal.Width)))

ggplot(df, aes(x = Species, y = y)) +
  geom_bar(position = "dodge", color="black", stat="identity") + 
  labs(x = expression(alpha["100"]), y=expression(paste(rho["1"], ", ", rho["2"]))) 

f:id:teramonagi:20190831120015p:plain