GoogleでSurfingkeysのgiが効かないことへの対応

ブラウザの操作のためにChrome拡張のSurfingkeys(GitHub - brookhong/Surfingkeys: Map your keys for web surfing, expand your browser with javascript and keyboard.)を入れているが、最近Googlegi(最初の入力欄にフォーカスするコマンド)が効かなくなって少々困っていたが、色々確認していたらi(入力欄にフォーカスする、画面内に複数ある場合は選択肢が表示される)なら効くということがわかったので、設定に次の記述を追加することで対応した。

// Googleでgiコマンドの動作を修正する
if (window.location.hostname === 'www.google.com' || window.location.hostname === 'www.google.co.jp') {
  api.map('gi', 'i');
}

それだけの話で大した修正でもないんだけど、こうすれば修正できるということはChatGPTと雑談していたら見つけた。1発で解決方法を提示してくれたわけではないものの、ダメだったと伝えたり手がかりっぽい情報を伝えたりするとすぐに他の案を出してくるので割とスムーズに解決策にたどり着けて体験が良かった。

Rで非等価結合 (2)

dplyrが1.1.0になった。これにより、Rで非等価結合 - もうカツ丼はいいよなで少し触れていたdplyrによる非等価結合が正式にサポートされた。

記事で取り上げていた例なら次のように簡潔な記述で結合できるようになった。

# データの準備
library(dplyr)
car <- tibble(
  car_model = c("CarA", "CarB", "CarC"),
  car_price = c(20000, 30000, 50000)
)
boat <- tibble(
  boat_model = c("Boat1", "Boat2", "Boat3"),
  boat_price = c(10000, 40000, 60000)
)

# car_price > boat_priceを条件とした非等価結合
car %>%
  left_join(
    boat,
    by = join_by(car_price > boat_price)
  )

## # A tibble: 4 × 4
##   car_model car_price boat_model boat_price
##   <chr>         <dbl> <chr>           <dbl>
## 1 CarA          20000 Boat1           10000
## 2 CarB          30000 Boat1           10000
## 3 CarC          50000 Boat1           10000
## 4 CarC          50000 Boat2           40000

また、上記のような単純な不等号を使った非等価結合以外にrolling join、overlap joinができるようになっている。これによっておそらく時系列データなどがかなり扱いやすくなるのではないかと思われる。

Rolling join

これは不等号を使った非等価結合であるが、結合の結果を最も「近い」1件に限定する(タイが存在すると1件になるとは限らないが、その場合の挙動は後述)。

例のデータならCarCに対応する部分の結合結果が1行に減る。

car %>% 
  left_join(
    boat,
    by = join_by(closest(car_price > boat_price)) # 条件をclosest()で囲む
  )

## # A tibble: 3 × 4
##   car_model car_price boat_model boat_price
##   <chr>         <dbl> <chr>           <dbl>
## 1 CarA          20000 Boat1           10000
## 2 CarB          30000 Boat1           10000
## 3 CarC          50000 Boat2           40000

Overlap join

結合条件に範囲を使うもの。連続値を離散値に変換するような場合に便利だろう。

price_class <- tibble(
  class = c("A", "B", "C"),
  lower = c(10000, 20000, 50000),
  upper = c(19999, 49999, 100000)
)
car %>% 
  left_join(
    price_class,
    by = join_by(between(car_price, lower, upper)) # 条件をbetween()で記述する
  )

## # A tibble: 3 × 5
##   car_model car_price class lower  upper
##   <chr>         <dbl> <chr> <dbl>  <dbl>
## 1 CarA          20000 B     20000  49999
## 2 CarB          30000 B     20000  49999
## 3 CarC          50000 C     50000 100000

Overlap joinは条件に指定した変数の値次第では複数の行にマッチしてしまい、行が増える可能性がある。それが意図しないものだった場合は困るので、複数行にマッチするケースが存在したらエラーを返すようにすることもできる。

price_class <- tibble(
  class = c("A", "B", "C"),
  lower = c(10000, 20000, 50000),
  upper = c(20000, 50000, 100000) # CarAとCarCが複数のclassに属する
)
car %>% 
  left_join(
    price_class,
    by = join_by(between(car_price, lower, upper)),
    multiple = "error" # 複数のマッチがある場合にエラーを返すようにする。
  )

## Error in `left_join()`:
## ! Each row in `x` must match at most 1 row in `y`.
## ℹ Row 1 of `x` matches multiple rows.

複数行マッチ時のwarning

等価結合の場合とrolling joinの場合、左辺のテーブルの特定の行に右辺のテーブルの複数行がマッチして結果の行が増えてしまうことがあり得るが、それは一般的に意図しないものであろう、ということでそういう場合に警告が出るようになった。

df1 <- tibble(id = c(1, 2, 3), x = c("a", "b", "c"))
df2 <- tibble(id = c(1, 1, 2, 3), y = c("w", "x", "y", "z"))
left_join(df1, df2, by = "id")

## Warning in left_join(df1, df2, by = "id"): Each row in `x` is expected to match at most 1 row in `y`.
## ℹ Row 1 of `x` matches multiple rows.
## ℹ If multiple matches are expected, set `multiple = "all"` to silence this
##   warning.

## # A tibble: 4 × 3
##      id x     y    
##   <dbl> <chr> <chr>
## 1     1 a     w    
## 2     1 a     x    
## 3     2 b     y    
## 4     3 c     z

ちょっと気を利かせすぎな気もするが、実際このようなケースは意図せず生じた場合に検出が難しく間違いにつながることがしばしばある。私も何度かハマった経験がある。なのでデフォルトで警告が出るくらいのが良いのだろう。

警告は先程も使用した引数multiple"all"を指定することでoffにできる。

left_join(df1, df2, by = "id", multiple = "all")

Cross join (交差結合)

他に結合関係の変更として、今まではleft_join(x, y, by = character())というあまり直感的ではない書き方をする必要があった交差結合がcross_join()によってできるようになった(by = character()を指定して交差結合を行うと、cross_join()を使うよう警告が表示される)。

cross_join(car, boat)

## # A tibble: 9 × 4
##   car_model car_price boat_model boat_price
##   <chr>         <dbl> <chr>           <dbl>
## 1 CarA          20000 Boat1           10000
## 2 CarA          20000 Boat2           40000
## 3 CarA          20000 Boat3           60000
## 4 CarB          30000 Boat1           10000
## 5 CarB          30000 Boat2           40000
## 6 CarB          30000 Boat3           60000
## 7 CarC          50000 Boat1           10000
## 8 CarC          50000 Boat2           40000
## 9 CarC          50000 Boat3           60000

その他

  • dplyr 1.1.0での変更点はおそらくこの記事dplyr 1.1.0 is coming soonが現状だとよくまとまっている。一般的にはsummarise().byによる一時的なグループ化ができるようになったことあたりが役立つのではないかと思う。
  • dbplyr 2.3.0においてはまだjoin_by()はサポートされていない。Pull request(Support `join_by()` by mgirlich · Pull Request #1074 · tidyverse/dbplyr · GitHub)が上がっているのでそのうち対応されるだろうと思う。備えよう。

Scrapboxで画像が多いページへの行リンクがずれないようにする

画像の高さが不定なので、画像の読み込みが遅れると最初の表示位置からスクロールがずれてしまうらしい。

色々試したけどURLがハッシュを含む場合にウィンドウの高さが変わったら(≒画像が遅れて読み込まれたら)アンカーを叩くという方法で対応している。

let lastClientHeight = 0;
document.addEventListener("scroll", () => {
  if(lastClientHeight !== document.body.clientHeight) {
    if(location.hash != "") window.location.href = location.hash;
    lastClientHeight = document.body.clientHeight;
  }
});

個人のプロジェクトの場合はUserScriptに書いているが、共同プロジェクトの場合はUserScriptを切っているのでChrome拡張に書いている。

参考

DTでNA、Infを扱う

RのDTパッケージを使うとJavaScriptのDataTablesライブラリ(DataTables | Table plug-in for jQuery)利用したインタラクティブなテーブルが作成できる。

しかし、JavaScriptのライブラリである都合上、Rでは可能なことが簡単には実現できないということもある。表題に挙げたNAInfの扱いもその一つ。

続きを読む

指数平滑移動平均における初期値の決定・調整方法とR、Pythonでの計算方法

指数平滑移動平均移動平均の一種で、数列のある値y_tに対応する移動平均S_tを、次のような漸化式で定める。


S_t = \alpha y_t + (1-\alpha)S_{t-1}

ここで\alphaは新しい値に対する重みの割合を調整する係数で、0 \lt \alpha \lt 1であり、1に近いほど直近の値の影響が大きくなる。

漸化式を級数展開して、次のような表現をすることもできる。


S_t = \alpha (y_t + (1-\alpha)y_{t-1} + (1 - \alpha)^{2} y_{t-2} + (1-\alpha)^{3} y_{t-3}  + ... )

y_tに近いほど重みが大きく、遠い値の重みが指数関数的に減衰していくことが分かる。

指数平滑移動平均はExponential Moving Averageの頭文字をとってEMAと呼ばれることが多いので、以下EMAとする。

続きを読む

Rで非等価結合

非等価結合 is 何

通常の結合(等価結合)においては、テーブル同士を結合する場合にそれぞれのテーブルから列を指定し、列の値が等しいという条件をもって行同士を結合する。

非等価結合は結合条件に不等式や範囲などの等値ではない条件を指定して結合する。非等結合、非等値結合、Non-equi Join、θ-Joinなどと呼ばれることもある。

Wikipediaのθ-Joinの説明(Relational algebra - Wikipedia)を例にする。

次のようなテーブルがあるとする。

car_model car_price
CarA 20000
CarB 30000
CarC 50000
boat_model boat_price
Boat1 10000
Boat2 40000
Boat3 60000

これらのテーブルから、car_price > boat_priceとなるような組合せを得るということを考える。それには、1つ目のテーブルに対して、car_price > boat_priceを条件とした結合を行えば良い。結果は次のようになる。

car_model car_price boat_model boat_price
CarA 20000 Boat1 10000
CarB 30000 Boat1 10000
CarC 50000 Boat1 10000
CarC 50000 Boat2 40000

このような結合は、SQLであれば結合条件にon car_price > boat_priceのような指定をすることで実現できる。ではRではどうすれば良いか?

続きを読む

cVimからSurfingkeysに乗り換えた

Vimperatorが使えなくなってから長いことcVimを使っていたが、cVimもメンテナンスされなくなり、cVimをforkしたvb4cも更新されないままついにarchiveになってしまった。

というわけで乗り換え先を色々検討して、最終的にはSurfingkeysにした。

github.com

他にも色々検討したので調べたことをメモっておく。また、Surfingkeysの設定は記事の末尾にリンクを置いた。

続きを読む

RStudioにショートカットを追加する(shrtctsパッケージ)

前置き

RStudioはキーボードショートカットのカスタマイズができる。

だが、好きなキーボードショートカットを追加しようとするとRStudioの設定だけでは完結しない。具体的にはアドインを作る必要がある。アドインにはショートカットが設定できるので、目的の動きをするアドインを作ってキーボードショートカットを割り当てれば良い。

しかしアドインを作るのはそこそこ面倒だ。そこで今回紹介するshrtctsパッケージだ。このパッケージを使えばアドインを自作することなく*1、ショートカットを追加できる。

pkg.garrickadenbuie.com

*1:中身を軽く見てみたところ、設定ファイルに基づいて関数を定義し、その関数をshrtctsパッケージのアドインとして登録することで目的を達成しているようだ。賢い。

続きを読む