まず、4つしか変数がないのに一行で2つの欠損があるようなデータは悪条件ですから、ある意味では多少の妥協は必要になります。
次に、max_iter
を増やしたり、max_value
, min_value
を指定したりすることで多少の改善が見込めます。そんなに大げさな改善にはならないと思いますが。
最後に、これが一番深刻な問題ですが、IterativeImputer
が内部で用いている回帰モデルはベイジアンリッジで、線形モデルになります。性別・年齢・身長・体重の関係が線形ということは考えづらいので、予測に限界があります。
こういうとき、非線形回帰系のモデルを手軽に突っ込めると助かるのですが、IterativeImputer
で使える回帰モデルには
If sample_posterior is True, the estimator must support return_std in its predict method.
sklearn.impute.IterativeImputer — scikit-learn 0.21.3 documentation
というきつい制約があり、条件を満たす非線形回帰手法のモデルは軽く探した範囲だとGaussianProcessRegressor
くらいしかありません。やってみましたがチューニングがよくわからないし、すぐ警告を吐くのでちょっと難があります。ちゃんと使えばたぶんできます。
一例ですが、私が使ってなんとかまともにうごいたかなという気がした定義です。パラメータの妥当性は保証しません。
python
1# 追加で必要になるimportのみ記載。以下のコードでも同じ
2from sklearn.preprocessing import StandardScaler
3from sklearn.gaussian_process import GaussianProcessRegressor
4from sklearn.pipeline import Pipeline
5
6ss = StandardScaler()
7gp = GaussianProcessRegressor(alpha=1e-5, normalize_y=True)
8est = Pipeline([("scaler", ss), ("reg", gp)])
9imp = IterativeImputer(est, max_iter=10, sample_posterior=True)
multiple imputationを諦めればSVRだろうがランダムフォレストだろうが使えるので、それはそれで楽ではあります。
python
1from sklearn.ensemble import RandomForestRegressor
2
3est = RandomForestRegressor(n_estimators=100, n_jobs=-1)
4imp = IterativeImputer(est, max_iter=10, sample_posterior=False)
理論的な良し悪しはともかく、こっちの方が良い結果が得られるなら採用するというのもありだと思います。
他の方法は、非線形回帰ができるように然るべき変換をするTransformerを作ってやることです。
どう変換するかというと、選択肢の一つは多項式回帰にして無理やり解くことです。やるとたしかにできますが、しょせん多項式なのでちょっと当てはまりが悪いかもしれません。試しましたが、年齢で100歳とか出ちゃったりして、愉快な感じでした。
python
1from sklearn.preprocessing import PolynomialFeatures, StandardScaler
2from sklearn.linear_model import BayesianRidge
3from sklearn.pipeline import Pipeline
4
5ss = StandardScaler()
6pf = PolynomialFeatures(degree=3, include_bias=False)
7br = BayesianRidge()
8est = Pipeline([("scaler", ss), ("poly_f", pf), ("reg", br)])
9imp = IterativeImputer(est, max_iter=10, sample_posterior=True)
もう少し正攻法で考えると、とりあえず身長体重は大雑把に言えば線形とみなしてよさそうで、性別は0か1ですからどうでもよく、加齢に対する成長の効果を適切に反映するような変換だけすればなんとかなる、という発想があり得るかもしれません。ということで、適当に作ったのがこれです。
python
1from sklearn.preprocessing import FunctionTransformer
2from sklearn.compose import ColumnTransformer
3from sklearn.linear_model import BayesianRidge
4
5def trans(x):
6 """ここ見ながら適当に作りました↓
7 https://www.graphsketch.com/
8 """
9 return 20-np.exp(-0.3*x+3)
10
11ft = FunctionTransformer(trans)
12ct = ColumnTransformer([("age", ft, [1])], remainder="passthrough")
13br = BayesianRidge()
14est = Pipeline([("scaler", ct), ("reg", br)])
15imp = IterativeImputer(est, max_iter=10, sample_posterior=False)
16
だいたい20歳以上で成長が止まるという状況を想定した関数を作ってやってますが・・・だいたいそれっぽくはなったけど、もう少し工夫が必要な気もしました。