Python
R
Jupyter
RMarkdown

RMarkdownからPython、いやJupyterを使う。

やりたいこと

RMarkdownは下記のようにすることで、pythonを呼び出して使うことが出来るが、これはチャンク毎に1個のセッションを呼び出しているので、結果は保存されない。

pythonを使うことは出来るが、チャンクをまたがった実行はできない.Rmd
このようにするとRmarkdownでpythonを使うことができる

```{python}
import sys
ver = sys.version
print(ver)
``` 

2.7.13 (default, Jan 19 2017, 14:48:08) 
[GCC 6.3.0 20170118]

ただし、チャンクの内容をファイルにして`python <チャンクの内容>`としているだけのようなので、

```{python}
import sys
import subprocess

print(sys.executable, sys.argv)
``` 

('/usr/bin/python', ['/tmp/Rtmp2hwE4t/chunk-code-56da38833dcb.txt'])

チャンクをまたがった記述などはできない

```{python}
print(ver) # 最初のチャンクで定義したver ➔ 無い
``` 

Traceback (most recent call last):
  File "/tmp/RtmpCqNMJ1/chunk-code-76ef3b9243de.txt", line 1, in <module>
    print(ver)
NameError: name 'ver' is not defined

Jupyter(IPython Kernel)のセッションは別に上げておき、これに接続して使うようにすることで、チャンクを乗り越えた処理が可能なのではないかと考えた。

追記:2017/11/28
コメントで追加情報ちょっとあります。
チャンクを乗り越えた処理をする、というのは、RStudio/reticulateを使うコトで出来る(更に、
pyとRの間をまたがった処理まで出来る)ようで、時期アップデートではそれがデフォルトになるのだとか。
使い方等、詳しくは@yutannihilationさんのコメントを参照

やったこと

下記のようなスクリプトを作成しておき

jupyter_wrap.sh
#!/bin/bash  -f

# 下記2行はpyenv 上にあるのでこうしているが
# 普通にパスが通っているなら不要
source /etc/profile.d/pyenv.sh
pyenv shell anaconda3-5.0.1

SESSION_FILE_NAME=${HOME}/session.json

if [ -f $SESSION_FILE_NAME ]; then
    # session found
    jupyter run --existing $SESSION_FILE_NAME $1
else
    # session start
    python -m ipykernel_launcher -f $SESSION_FILE_NAME > /dev/null &

    # waiting for ipykernel start...
    while  [ ! -f $SESSION_FILE_NAME ]; do
        sleep 0.2
    done

    # then, run
    jupyter run --existing $SESSION_FILE_NAME $1
fi

この用にオプションを設定しておくことで、

pythonエンジン用のオプション(パス)を設定
```{r  setup, echo=FALSE}
# どうも、setup、という名前のチャンクじゃないと駄目っぽい・・?
library(knitr)
opts_chunk$set(engine.path = '/path/to/jupyter_wrap.sh')
``` 

こんな感じで、チャンクをまたいで処理してくれるはず。

```{python}
import sys
ver = sys.version
print(ver)
``` 

3.6.3 |Anaconda, Inc.| (default, Oct 13 2017, 12:02:49) 
[GCC 7.2.0]

```{python}
print(ver) # 最初のチャンクで定義したver ➔ ある!
``` 

3.6.3 |Anaconda, Inc.| (default, Oct 13 2017, 12:02:49) 
[GCC 7.2.0]

これってつまりJupyter notebookもipython_kernelのクライアントの一つに過ぎず、似たようなことをRMarkdownでやっている感じなんじゃないかな。
おそらくpython以外のkernelでも出来るのではないかという気がする。

また、残念ながら、標準入出力を介したやり取りしかできないが、RStudio/reticulateを使うことで、pythonのオブジェクトや、図などもやり取り出来るかもしれない。
参考:https://stackoverflow.com/questions/36437028/rstudio-with-python-matplotlib-graph

Pandas DataFrameと、RのDataFrameの間はArrowとかFeatherとかでできそう。

なんでわざわざRMarkdownを?と思うかもしれないが、gitとの相性とかを考えるとRMarkdownいいなーでも、pythonのほうが情報多いし、出来ることも違いそうだなーと思って色々と調べていたのでした。
tidyverseとpipeRは面白いです、ワンライナー好きにはたまらない・・・。

補足1

最初に調べたのは、Jupyterのセッションに接続する方法。
調べたところ、Jupyterのセッションに接続するとはこのような感じである。

$ jupyter console # notebook ではなく、consoleで起動

として

jupyter console 

Jupyter console 5.2.0

Python 3.6.3 |Anaconda, Inc.| (default, Oct 13 2017, 12:02:49)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: %connect_info
{
  "shell_port": 33095,
  "iopub_port": 56161,
  "stdin_port": 60865,
  "control_port": 41975,
  "hb_port": 45665,
  "ip": "127.0.0.1",
  "key": "7cec19ae-ae48a07596105f9c998f369e",
  "transport": "tcp",
  "signature_scheme": "hmac-sha256",
  "kernel_name": ""
}

Paste the above JSON into a file, and connect with:
    $> jupyter <app> --existing <file>
or, if you are local, you can connect with just:
    $> jupyter <app> --existing kernel-12421.json
or even just:
    $> jupyter <app> --existing
if this is the most recent Jupyter kernel you have started.

In [2]: a=10

メッセージにある通り、別のシェルなどから

$ jupyter console --existing

とすると、別のipython consoleが立ち上がり、先程のセッションで定義したaを見ることが出来る。

Jupyter console 5.2.0

Python 3.6.3 |Anaconda, Inc.| (default, Oct 13 2017, 12:02:49)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: a
Out[2]: 10

consoleではなくて、スクリプトファイルを渡したりして実施したい場合は

$ jupyter run --existing - <ファイル名>

とすることでできた。

補足2

ここまで調べて、実際にやったことと同じように、pythonの実行パスとオプションで対処しようとした所、幾つか上手く行かないことがあった。

  • --existingの後を空(-)にして渡すのが何故かうまくいかない
    • ➔ 明示的にセッションファイルを作成する(run -f オプション)を使うことに
  • Jupyterの起動もついでにやっちゃろうと、jupyter_wrap.shのような物を作ったのだが、色々試して見たところどうしてもjupyter runが親プロセスと一緒に死んでしまう(親プロセスがなくなったことを検出して自動的にシャットダウンしたかのようなメッセージが出る)ので、ipython_kernelを直接立ち上げたところ、上手く行った。

なお、ipython_kenelを終了するには、RMarkdownで

```{python}
quit()
``` 

とするか、シェルからjupyter consoleなどで接続して、

In [1]:  quit

とする。

Rのセッションをリスタートしたときとかに、一緒に消えてくれたりはしない。