Python/PandasのdfをSTATAのdatasetに入れる方法 （関数の定義も）

#1, 導入
最近になってSTATAを使い始めるようになったので、軽い気持ちで「STATA上でpythonを使ってデータ分析する方法」を模索し始めたのですが、そもそもどうやってSTATAとpythonを連携さえるかの段階でつまづいたりして、結構苦労しました。
特に、PythonにおいてPandas dfで編集したデータセットをどうやったらSTATAのdatasetに格納できるのかが分からなくて、結構悩みました。それができればSTATAとPythonの連携効率がかなり上がると思ったのですが、、。

ということで、今回色々調べて分かった「STATA上でpythonを開いてデータ分析を行うときに知っておくべきこと」をまとめてみました。特に拘った点は、PandasのdfをSTATA datasetに移す方法の整理です。

#2, 環境
STATA /MP16.1 2 core
Python 3.8.5
Pandas 1.2.1

#3, STATAとPythonを連携させる方法
僕が理解している範囲では、STATAとPythonの連携方法は3パターンあります。
**①STATA上でPythonを呼び出す**

https://blog.stata.com/2020/08/18/stata-python-integration-part-1-setting-up-stata-to-use-python/

**②jupyter notebookでSTATAカーネルとして使う**

https://kylebarron.dev/stata_kernel/getting_started/

**③Python上でSTATAを呼び出す、ipystataというモジュールを使う**

https://github.com/TiesdeKok/ipystata


理想は③だったのですが、いろいろやってみてダメでした。
ということで、①のやり方でSTATA上でPythonを開くことにしました。

#4, STATAでPythonを使うときによく使う超基本的なコマンド
**①STATAコマンド**
STATAコマンド専用のCode記述ができないので、代わりにPythonのものを用いましたが、実際は以下のコマンドはSTATAのものです。

```python
#ディレクトリの替え方（初期設定のディレクトリの位置はCドライブの頭？）
#ディレクトリの順番とかは関係なく、指定したところに飛べる
cd "C:\Users\handsome\gojo"

#csvファイルを開くコマンド
insheet using C:\Users\handsome\gojoファイル名.csv

#STATA datasetに格納されたデータセットだけを消すコマンド
clear

#STATAのメモリに入ってるすべて（Pythonのdfや関数なども含めてっぽい）を消すコマンド
clear all

#記述統計
summarize

#OLS
regress y x1 x2 x3

#IV
ivregress 2sls y x1 x2 (end1 end2 = exo1 exo2)

#pythonの開き方
python #開く
end #閉じる
```

まあ、その他のコマンドは、一回GUIでやってみて確認すればいいですね。
上記コマンド最後のpythonの開き方は最重要です。
「python」で開いた後、そのままいろいろ操作して、pythonを閉じたくなったら「end」で閉じます。
なお、一回pythonを呼び出してしまうと、上記コマンドはすべて使えなくなりますが、以下のマジックコマンドみたいなやつを使うと、STATA上でPythonを開きながらSTATAのコマンドを使えます。

**②STATA上でPythonを開いた後に、そのままSTATAコマンドを使う方法**

```python
stata: ~
(stata: sysuse auto  とか)
```

#5, Python/Pandas→STATAの方向にデータを受け渡す方法の考察
これが本エントリーの本題です。
理想は、PythonにおけるPandasのdfでデータフレームをいじくり回して、そのdfをSTATA datasetに変換してSTATAの豊富な統計パッケージを使うことです。
簡単なやり方としては、別々にPythonとSTATAを開いて、Pythonで加工し終わったデータを.csvや.dtaに変換してSTATAに渡す方法があると思いますが、サンプルサイズが大きいデータから複数のサブサンプルを抜き出して別のファイルとして保存して、、みたいなことを繰り返すのはパソコンの寿命を減らしそうな気がするし、なんとなく手間がかかるから嫌でした。
最終的に、色々調べた末に何とか解決できたので、その方法をご紹介します。また、定義した関数も例として残しておきます。
本題に入る前に、STATAとPythonの連携に絶対必要なsfiというモジュールの説明から入ります。

##sfiというモジュール
↓これが公式の説明です。

https://www.stata.com/python/api16/index.html

このモジュールの機能を一言で説明するなら、「StataのdatasetをPythonで弄ったり、Pythonのnumpy/pandasのdfをdatasetに入れるためのモジュール」です。
特に新しくインストールする必要はないみたいで、初期の状態でも入っているよう（？）です。
このモジュールを挟むことによって、PythonでSTATA datasetを弄ることができるみたいです。
ただ、僕にはちょっとわかりづらかったので、Pandasでdfを完成させて、完成したdfをSTATA datasetに変換するのがベストじゃないでしょうか。
Pandas dfをSTATA datasetに入れる場合も、sfiを使うようです。
以下、他の方が公開した方法を参考にして、「PandasのdfをSTATA datasetとして認識させる」関数を定義しました。

##「PandasのdfをSTATA datasetに入れる」関数

https://www.statalist.org/forums/forum/general-stata-discussion/general/1507057-storing-a-database-from-python-into-a-stata-dataframe

↑このSTATA公式Q&Aにおける回答者の方のコードを参考にして、以下の関数を定義してみました。迷いましたが、forループ内部は、一部改変したものの（intやfloatのブールのところ）、ほとんど原文のままコピペで使わせて頂きました。
ついでに、この質問者さんが例として作ったdfを使った実行結果のテスト方法も示しました。

**※以下、STATAのdoファイルを開いて、doファイル上で「実行」することを想定しています。Python（pandasとnumpyを入れてるやつ）を開けるSTATAを持っている方はコピペして試してみてください**

```python
clear all
python

#モジュールのインポート
import numpy as np
import pandas as pd
from sfi import Data, SFIToolkit

#　Python pandasのdfからSTATA datasetにデータを渡す関数
def datapass(data):
    #　インプットのデータセットを一般化する
    df = data.copy()
    
    Data.setObsTotal(len(df))

    # get the column names
    colnames = df.columns

    for i in range(len(colnames)):
        dtype = df.dtypes[i].name
        # make a valid Stata variable name
        varname = SFIToolkit.makeVarName(colnames[i])
        varval = df[colnames[i]].values.tolist()
        if "int" in dtype:
            Data.addVarInt(varname)
            Data.store(varname, None, varval)
        elif "float" in dtype:
            Data.addVarDouble(varname)
            Data.store(varname, None, varval)
        elif dtype == "bool":
            Data.addVarByte(varname)
            Data.store(varname, None, varval)
        else:
            # all other types store as a string
            Data.addVarStr(varname, 1)
            s = [str(i) for i in varval] 
            Data.store(varname, None, s)
#　関数datapassの定義終わり


#　適当なdfでテストしてみる
data = {'school': ['UCSC', 'UCLA', 'UCD', 'UCSB', 'UCI', 'UCSF'],'year': [2000, 2001, 2002, 2001, 2002, 2003], 'num': [4000, 3987, 5000, 4321, 5000, 8200]}
ds = pd.DataFrame(data)

datapass(ds)   #　関数datapassを使用

#　Pythonを呼び出しながらSTATA(ここでは記述統計)の機能を使う
stata: summarize

end
```
###使い方
まず、STATA上でPythonを呼び出して、その中でpandas dfの処理を全て完結させてください。
上記定義関数の下にデータ編集用のコードをくっつければいいと思います。
pandasを用いたデータの加工が終わったら、次はSTATA datasetにそのdfを格納します。
例えば、dfの変数名を「ds」としておきます（変数名は何でもいいです）。
上記の関数「datapass()」にdsを引数として入れて（datapass(ds)）、今まで作っていたpandas dfをSTATA datasetに入れます。
そのあとは、pythonを「end」で閉じてGUIを使うなり、そのまま「stata: 」で回帰式などを推定するなり、ご自由に分析してください。

###注意点
**①データのdtypeはint, float, bool, objectだけ使うように。厳密には、uintなどを使いたい場合は、forループ内部のdtypeのブールの部分をを少し変えてください。まあ、uintなら大丈夫だとは思うけど、、、**
**②stata: summarizeは記述統計のSTATAコマンドです。「stata: 」を使わないと、pythonを呼び出してるときにSTATAコマンドは使えないので注意。**
**③doファイルを実行するときは、コピペしてSTATAの下の方にあるコマンドに直接入力しないように。doファイル内で実行ボタンを押してください。コピペしてコマンドに直接入力すると、for ループ内部に謎のエラーが出ます。**