LiNGAMによる因果探索では関数形に注意しよう

R
Japanese
公開

2024年3月10日

LiNGAMによる因果探索

LiNGAM(Linear Non-Gaussian Acyclic Model)は代表的な因果探索手法の一つで,近年は市販のソフトウェア等にも実装されるなど実務での活用が進んでいるようです.LiNGAMは,その名の通り,関数形が線形かつ誤差項がガウス分布(正規分布)以外に従う場合に,データから因果関係を明らかにすることができる手法です.

LiNGAMは,PythonのlingamパッケージやRのpcalgパッケージで利用することができ,これらのパッケージで提供される関数にデータフレームを入力すれば,簡単に因果グラフを推定することができます.因果グラフとは,観測変数をノード,因果関係を矢印に見立てて構築されるネットワーク構造のことです.特に,因果探索では,因果グラフとしてDAG(Directed Acyclic Graph)と呼ばれる有向非巡回グラフを推定することが一般的です.

因果グラフの例として,以下にレストラン経営に関する因果グラフを作成してみました.実務においては,曜日・天気・出店地域などの様々な要因によって利益が 変動すると考えられ,さらに複雑な因果関係が分析対象となることが想定されます.

flowchart LR
  食材の質 --> 料理の美味しさ
  食材の質 --> 原価
  料理人の腕 --> 料理の美味しさ
  料理人の腕 --> 給与
  料理の美味しさ --> 来客数
  料理の美味しさ --> 客単価
  来客数 --> 売上
  客単価 --> 売上
  売上 --> 利益
  原価 --> 費用
  給与 --> 費用
  固定費用 --> 費用
  費用 --> 利益

関数形がLiNGAMの推定に与える影響について

LiNGAMは,関数形が線形かつ誤差項がガウス分布以外に従うという前提のもとで因果関係を推定する手法です.そのため,関数形が線形でない(線形近似が難しい)場合には,誤った因果関係が推定される可能性があります.パッケージや市販のソフトウェアを使えばLiNGAMを簡単に実行することができてしまいますが,利用者はこうした前提があることを理解してLiNGAMを使う必要があると思われます.

ここでは,上に挙げた因果グラフよりも簡単な以下のような因果関係をもつデータに対してLiNGAMを適用してみましょう.具体的には, 以下のフローチャートのfunに,非線形な関数が入ったときにLiNGAMの結果がどのように変わるかを確認します.

ただし,この記事では最も一般的なLiNGAMであるDirect LiNGAM,誤差項として非ガウス分布の一様分布を用いています. また,係数の絶対値が0.001未満の因果関係は無視するものとしました.

flowchart LR
  x11 --> add1(+)
  x12 --> add1
  add1 --> x21
  x21 --> fun2(fun)
  x22 --> fun2
  fun2 --> x31
style fun2 stroke:red,color:red

因果グラフの推定に先立ってx11x12x22列をもつ1,000行のデータフレームおよび因果探索用の関数を用意しておきましょう.

コード
import numpy as np
import os
import pandas as pd
import lingam

rng = np.random.default_rng(1234)

n = 1000
data = pd.DataFrame({
  'x11': 1 + rng.random(n),
  'x12': 2 + rng.random(n),
  'x22': 3 + rng.random(n)
})

# DirectLiNGAMの結果をデータフレームとして返す関数
def discover_causality(data):
  model = lingam.DirectLiNGAM()
  model.fit(data)

  return pd.DataFrame(
    model.adjacency_matrix_,
    columns=data.columns,
    index=data.columns
  )\
  .reset_index(names = 'node_to')\
  .melt(
    id_vars='node_to',
    var_name='node_from'
  )\
  .pipe(lambda df: df[np.logical_not(np.isclose(df.value, 0, rtol=0, atol=1e-3))])\
  .reindex(columns=['node_from', 'node_to', 'value'])

# mermaidファイルを出力する関数
def write_mermaid(df, file):
  with open(file, 'w') as f:
    f.write('flowchart LR\n')
    for row in df.itertuples():
      f.write('  {}-->|{:.3f}|{}\n'.format(row.node_from, row.value, row.node_to))

# 出力先のフォルダ
dir = 'be-careful-with-function-forms-in-lingam'
if not os.path.exists(dir):
  os.makedirs(dir)

因果グラフに掛け算が含まれるケース(fun*

funが掛け算(*)のときにDirect LiNGAMの推定結果がどのようになるかを見てみましょう. 推定された因果グラフは真の因果グラフと同等の構造をもっており,今回のケースでは,因果グラフが正しく推定されていることがわかります. このように,足し算(線形)でなく掛け算のケースでも因果関係の推定がうまくいくケースがあるようです. ただし,観測変数の数やデータ数によっては状況が大きく異なるかもしれません.

data_nonlinear_prod = data\
 .assign(
    x21=lambda df: df.x11 + df.x12 + rng.random(n),
    x31=lambda df: df.x21 * df.x22 + rng.random(n),
  )

causality_nonlinear_prod = discover_causality(data_nonlinear_prod)
print(causality_nonlinear_prod)
   node_from node_to     value
3        x11     x21  0.975641
8        x12     x21  0.999905
14       x22     x31  4.524066
19       x21     x31  3.477884
コード
write_mermaid(causality_nonlinear_prod, os.path.join(dir, 'dag_lingam_nonlinear_prod.mmd'))

flowchart LR
  x11-->|0.976|x21
  x12-->|1.000|x21
  x22-->|4.524|x31
  x21-->|3.478|x31

因果グラフにべき乗が含まれるケース(fun**

次に,funがべき乗(**)のときにDirect LiNGAMの推定結果がどのようになるかを見てみましょう. 推定された因果グラフは真の因果グラフと異なる構造をもっており,もともと因果関係が存在しない上流の観測変数間にも誤った因果関係が推定されていることがわかります.このように,Direct LiNGAMに非線形な関数が含まれる場合には,その関数と直接関係しない観測変数間においても,誤った因果関係が推定されてしまうリスクがあることがわかります.

data_nonlinear_power = data\
 .assign(
    x21=lambda df: df.x11 + df.x12 + rng.random(n),
    x31=lambda df: df.x21 ** df.x22 + rng.random(n),
  )

causality_nonlinear_power = discover_causality(data_nonlinear_power)
print(causality_nonlinear_power)
   node_from node_to     value
1        x11     x12 -0.147973
3        x11     x21  0.832946
8        x12     x21  0.798157
17       x21     x22 -0.400541
22       x31     x22  0.002306
コード
write_mermaid(causality_nonlinear_power, os.path.join(dir, 'dag_lingam_nonlinear_power.mmd'))

flowchart LR
  x11-->|-0.148|x12
  x11-->|0.833|x21
  x12-->|0.798|x21
  x21-->|-0.401|x22
  x31-->|0.002|x22

まとめ

この記事では,非線形な関数をもつデータに対してLiNGAMを適用すると,誤った因果関係が推定される可能性があることを示しました. そのため,LiNGAMの適用にあたっては,因果関係が線形で表せることを確認することが重要です.

一方で,因果関係がわからない状況なのに,因果関係が線形で表せることがわかるという状況はまれであると思われます. そのため,事後的な線形性の確認や実験などを通じて,慎重に因果関係を特定することが求められます.