ThSQCA 入門 第2回: 4つのスイープと、範囲の決め方

ThSQCA
tutorial
アウトカム、1つの条件、複数の条件、その両方をスイープします。練習用データで4つのスイープをすべて実行し、擁護できる閾値の範囲の決め方を考えます。
Author

Yuki Toyoda

Published

October 5, 2026

第1回では、アウトカム閾値スイープを実行して、結果を読みました。ThSQCAには、スイープの関数が全部で4つあります。この回では、同じ練習用データで4つすべてを実行し、最後に、閾値の範囲の選び方についての、実践的な助言を述べます。

4つのスイープの一覧

問い 関数 動かすもの 固定するもの
アウトカムの目標水準が上がると、解はどう変わるか otSweep() アウトカムの閾値 すべての条件の閾値
1つの条件の「高い」の定義に、どれだけ敏感か ctSweepS() 1つの条件の閾値 アウトカムと、ほかの条件
複数の条件の定義が、一緒に変わったらどうなるか ctSweepM() 複数の条件の閾値(格子状) アウトカムの閾値
目標水準と、条件の定義の両方が変わったらどうなるか dtSweep() アウトカムと条件 なし

準備: 第1回と同じ練習用データ

library(ThSQCA)
練習用データ(第1回と同じ)
make_demo_data <- function(n = 400, seed = 2026) {
  set.seed(seed)
  clip <- function(x) pmin(10, pmax(0, round(x)))
  QUA <- clip(rnorm(n, 6, 2))
  SER <- clip(rnorm(n, 6, 2))
  ENV <- clip(rnorm(n, 6, 2))
  core <- pmax(pmin(QUA, SER), pmin(QUA, ENV) - 1)
  LOY  <- clip(core + rnorm(n, 0, 0.7))
  data.frame(LOY, QUA, SER, ENV)
}

demo <- make_demo_data()

データの背景を思い出してください。品質(QUA)とサービス(SER)がどちらも高いとき、または、それより少し弱く、品質と店舗環境(ENV)がどちらも高いときに、ロイヤルティ(LOY)が高くなります。

スイープ1: ctSweepS() で、1つの条件を動かす

「サービスが高い」の線をどこに引くか、迷っているとします。6で十分でしょうか。7でしょうか。9でしょうか。アウトカムを LOY >= 7 に固定し、ほかの条件を7に固定して、SER の閾値を5から9まで動かします。

res_cts <- ctSweepS(
  dat          = demo,
  outcome      = "LOY",
  conditions   = c("QUA", "SER", "ENV"),
  sweep_var    = "SER",
  sweep_range  = 5:9,
  thrY         = 7,
  thrX_default = 7
)

summary(res_cts)
CTS (single) Summary
==================== 

Analysis Parameters:
  Outcome: LOY 
  Conditions: QUA, SER, ENV 
  Consistency cutoff: 0.8 
  Frequency cutoff: 1 

Results by Threshold:

 threshold   expression inclS  covS n_solutions
         5  No solution    NA    NA           0
         6  No solution    NA    NA           0
         7      QUA*SER 0.894 0.656           1
         8      QUA*SER 0.909 0.333           1
         9 QUA*SER*~ENV 0.917 0.122           1

新しい引数は、sweep_var(閾値を動かす条件)、thrY(固定するアウトカムの閾値)、thrX_default(ほかのすべての条件の、固定する閾値)です。

表を上から下へ読みます。「サービスが高い」を5または6と定義すると、解はありません。定義がゆるすぎて、「サービスを十分に受けた」顧客の集団が、もはや確実にはロイヤルではなくなるからです。7と8では、解は QUA*SER です。9では、解に3つ目の要素が加わって QUA*SER*~ENV になり、カバレッジは、7での0.656から、9での0.122へと、大きく下がります。いちばん厳しい定義の背後に、顧客が何人いるかを数えると、理由が分かります。

sum(demo$QUA >= 7 & demo$SER >= 9)                 # 品質とサービスの両方が高い顧客
[1] 16
sum(demo$QUA >= 7 & demo$SER >= 9 & demo$ENV < 7)  # 解 QUA*SER*~ENV を満たす顧客
[1] 12

その水準で、品質とサービスの両方が高い顧客は、400人のうち16人です。そのうち、店舗環境が7未満で、解 QUA*SER*~ENV を満たす顧客は、12人だけです。そこでの結果は、ごく少数の事例に支えられています。これは役に立つ習慣です。解が格子の端にしか現れないときは、その背後にある事例の数を確かめてください。

スイープ2: ctSweepM() で、複数の条件を動かす

次に、3つの条件の閾値を、一度に動かします。QUA、SER、ENV のそれぞれを、6、7、8で試します。3 x 3 x 3 = 27通りの組み合わせです。アウトカムは LOY >= 7 のままです。

res_mcts <- ctSweepM(
  dat        = demo,
  outcome    = "LOY",
  conditions = c("QUA", "SER", "ENV"),
  sweep_list = list(QUA = 6:8, SER = 6:8, ENV = 6:8),
  thrY       = 7
)

出力は27行になります。読みやすくするために、解が出た組み合わせだけを表示します。

tab <- res_mcts$summary
tab[tab$n_solutions > 0, ]
   combo_id           threshold                  expression     inclS      covS
5         5 QUA=7, SER=7, ENV=6                     QUA*SER 0.8939394 0.6555556
6         6 QUA=8, SER=7, ENV=6                     QUA*SER 0.9189189 0.3777778
8         8 QUA=7, SER=8, ENV=6                     QUA*SER 0.9090909 0.3333333
9         9 QUA=8, SER=8, ENV=6                     QUA*SER 0.9047619 0.2111111
12       12 QUA=8, SER=6, ENV=7                QUA*SER*~ENV 0.8000000 0.3111111
14       14 QUA=7, SER=7, ENV=7                     QUA*SER 0.8939394 0.6555556
15       15 QUA=8, SER=7, ENV=7                QUA*SER*~ENV 1.0000000 0.3000000
17       17 QUA=7, SER=8, ENV=7                     QUA*SER 0.9090909 0.3333333
18       18 QUA=8, SER=8, ENV=7                QUA*SER*~ENV 1.0000000 0.1888889
21       21 QUA=8, SER=6, ENV=8                     QUA*ENV 0.8888889 0.1777778
23       23 QUA=7, SER=7, ENV=8                     QUA*SER 0.8939394 0.6555556
24       24 QUA=8, SER=7, ENV=8           QUA*SER + QUA*ENV 0.9200000 0.5111111
26       26 QUA=7, SER=8, ENV=8                     QUA*SER 0.9090909 0.3333333
27       27 QUA=8, SER=8, ENV=8 QUA*SER*~ENV + QUA*~SER*ENV 0.9393939 0.3444444
   n_solutions
5            1
6            1
8            1
9            1
12           1
14           1
15           1
17           1
18           1
21           1
23           1
24           1
26           1
27           1

注目したいパターンは、次のとおりです。

  • 27通りのうち、解があるのは14通りです。「品質が高い」を6と定義した組み合わせでは、解がまったくありません。
  • QUA が7で、SER が7または8のとき、ENV の閾値がどれであっても、解は QUA*SER です。これが安定した領域です。
  • QUA が8のとき、解は ENV(と SER)に左右されます。ENV が6なら、(SER が7または8のとき)QUA*SER です。ENV が7なら、QUA*SER*~ENV です。ENV が8なら、QUA*ENV だけのものから、QUA*SER + QUA*ENV のような2つの経路をもつ解まで、さまざまです。「品質が高い」を厳しく定義すると、店舗環境の定義が重要になり始めます。

格子を見ると、結論が頑健なところと、もろいところの両方が分かります。1回の分析では、これはできません。

スイープ3: dtSweep() で、アウトカムと条件を一緒に動かす

二重閾値スイープは、2つの考え方を組み合わせて、アウトカムの目標水準と条件の定義を、同時に動かします。ここでは、アウトカムの閾値を3つ(6、7、8)、条件の組み合わせを8つ(QUA、SER、ENV のそれぞれが6または7)にして、24のセルを作ります。

res_dts <- dtSweep(
  dat           = demo,
  outcome       = "LOY",
  conditions    = c("QUA", "SER", "ENV"),
  sweep_list_X  = list(QUA = 6:7, SER = 6:7, ENV = 6:7),
  sweep_range_Y = 6:8
)

res_dts$summary
   combo_id thrY                thrX        expression     inclS      covS
1         1    6 QUA=6, SER=6, ENV=6           QUA*SER 0.8630137 0.7118644
2         1    7 QUA=6, SER=6, ENV=6       No solution        NA        NA
3         1    8 QUA=6, SER=6, ENV=6       No solution        NA        NA
4         2    6 QUA=7, SER=6, ENV=6           QUA*SER 0.9215686 0.5310734
5         2    7 QUA=7, SER=6, ENV=6       No solution        NA        NA
6         2    8 QUA=7, SER=6, ENV=6       No solution        NA        NA
7         3    6 QUA=6, SER=7, ENV=6           QUA*SER 0.8932039 0.5197740
8         3    7 QUA=6, SER=7, ENV=6       No solution        NA        NA
9         3    8 QUA=6, SER=7, ENV=6       No solution        NA        NA
10        4    6 QUA=7, SER=7, ENV=6           QUA*SER 0.9848485 0.3672316
11        4    7 QUA=7, SER=7, ENV=6           QUA*SER 0.8939394 0.6555556
12        4    8 QUA=7, SER=7, ENV=6       No solution        NA        NA
13        5    6 QUA=6, SER=6, ENV=7           QUA*SER 0.8630137 0.7118644
14        5    7 QUA=6, SER=6, ENV=7       No solution        NA        NA
15        5    8 QUA=6, SER=6, ENV=7       No solution        NA        NA
16        6    6 QUA=7, SER=6, ENV=7 QUA*SER + QUA*ENV 0.9076923 0.6666667
17        6    7 QUA=7, SER=6, ENV=7       No solution        NA        NA
18        6    8 QUA=7, SER=6, ENV=7       No solution        NA        NA
19        7    6 QUA=6, SER=7, ENV=7           QUA*SER 0.8932039 0.5197740
20        7    7 QUA=6, SER=7, ENV=7       No solution        NA        NA
21        7    8 QUA=6, SER=7, ENV=7       No solution        NA        NA
22        8    6 QUA=7, SER=7, ENV=7 QUA*SER + QUA*ENV 0.9259259 0.5649718
23        8    7 QUA=7, SER=7, ENV=7           QUA*SER 0.8939394 0.6555556
24        8    8 QUA=7, SER=7, ENV=7       No solution        NA        NA
   n_solutions
1            1
2            0
3            0
4            1
5            0
6            0
7            1
8            0
9            0
10           1
11           1
12           0
13           1
14           0
15           0
16           1
17           0
18           0
19           1
20           0
21           0
22           1
23           1
24           0

読み方です。LOY >= 6 では、8通りの条件の定義のすべてで解があり、たいていは QUA*SER で、ときには QUA*SER + QUA*ENV です。LOY >= 7 では、解が残るのは、QUA と SER がどちらも7の、2通りだけです。LOY >= 8 では、どれにも解がありません。このデータでは、目標水準を上げることが、変化のいちばん強い要因です。

図にすると、同じことがもっと早く分かります。次のチャンクは、24セルすべての解のカバレッジを、ヒートマップで描きます。1行が条件の定義ひとつ、1列がアウトカムの閾値ひとつで、「none」は解のないセルです。図の中の語は英語のままです。

s <- res_dts$summary
m <- tapply(s$covS, list(s$combo_id, s$thrY), function(x) x[1])
lab <- as.character(s$thrX[match(rownames(m), s$combo_id)])

op <- par(mar = c(4.5, 11, 3, 1))
image(x = seq_len(ncol(m)), y = seq_len(nrow(m)), z = t(m),
      col = colorRampPalette(c("#FFFFFF", "#233A9A"))(50), zlim = c(0, 1),
      xaxt = "n", yaxt = "n", xlab = "Outcome threshold (thrY)", ylab = "",
      main = "Solution coverage in the 24 cells")
axis(1, at = seq_len(ncol(m)), labels = colnames(m))
axis(2, at = seq_len(nrow(m)), labels = lab, las = 1, cex.axis = 0.8)
for (i in seq_len(nrow(m))) for (j in seq_len(ncol(m))) {
  if (is.na(m[i, j])) text(j, i, "none", col = "grey50", cex = 0.8)
  else text(j, i, sprintf("%.2f", m[i, j]), cex = 0.8)
}
box()

8行(3つの条件の定義、それぞれ6または7)と3列(アウトカムの閾値6、7、8)のヒートマップ。閾値6の列は8セルすべてが塗られ、カバレッジは0.37から0.71。閾値7の列で塗られているのは、品質とサービスがどちらも7の行の2セルだけで、どちらも0.66。閾値8の列はすべて、noneと表示されている。

二重閾値スイープの24セルにおける、解のカバレッジ。色が濃いほどカバレッジが高く、noneのセルは解がありません。
par(op)

LOY >= 6 の列はすべての行で塗られ、LOY >= 7 の列は2行だけ、LOY >= 8 の列はどの行も塗られていません。

スイープの大きさ

すべてのセルが、1回分のQCA分析です。そのため、セルの数は、実行時間にも、結果の読みやすさにも関わります。

関数 分析の回数 例
otSweep() アウトカムの閾値ごとに1回 閾値5つ = 5回
ctSweepS() 動かす条件の閾値ごとに1回 閾値5つ = 5回
ctSweepM() 候補の数の積 3 x 3 x 3 = 27回
dtSweep() アウトカムの閾値 x 条件の格子 3 x (3 x 3 x 3) = 81回

擁護できる範囲を選ぶ

スイープは、簡単に実行できます。だからこそ、規律が必要です。たくさんの閾値を試して、都合のよいものだけを報告すれば、研究者の自由度を減らすどころか、増やしてしまいます。目安を挙げます。

  1. 結果を見る前に、範囲を決める。 尺度の意味、理論、実務上の目標(たとえば「許容できる」「良い」「とても良い」)に基づいて決めます。
  2. 基準点を含める。 通常の分析で使う閾値は、格子の中に入れておきます。読み手が比較できるようにするためです。
  3. 中立の領域を避ける。 0〜10の尺度では、中点のあたりの得点は、誰にとっても、はっきり「高い」とは言えません。そのため、範囲は、たいてい、それより上から始めます。
  4. 読める大きさに、格子を抑える。 変数ごとに、意味のある値を少し選ぶほうが、可能な値をすべて並べるより、よいです。
  5. 端の事例数を確かめる。 格子の端のセルは、ごく少数の事例に支えられていることが、よくあります。
  6. 格子全体を報告する。 ストーリーに合うセルだけでなく、安定した領域も、不安定な領域も、示します。

次回

第3回では、3種類の解(複雑解、倹約解、中間解)と、1つのセルに最小解が複数あるときの対処を扱います。

使ったソフトウェア: R 4.6.1、QCA 3.25.5、ThSQCA 2.0.9