条件と必要十分条件|論理・アルゴリズム・情報の基礎

このページはプロモーション(広告)を含みます。

論理・アルゴリズム・情報の基礎(初級・読む25分+演習30分)

この授業のゴール

仕事でどう使うか

仕事の会話では、「会員なら割引される」「データが欠損していたら除外する」のように、「〜ならば…」の形で話が進みます。この形の文が正しいか、逆向きも言えるのかを区別できないと、分析の結論や業務ルールの穴を見落とします。

路線 この授業の使いどころ
DS(データサイエンティスト) 「割引を受けた人は会員だった」というデータから、「会員だから割引を受けた」と結論してよいかを判断する。逆向きの文は別の主張
ML(機械学習エンジニア) ルールで作る特徴量(「解約済みなら理由コードがある」)が、結果側の情報を含んでいないかを点検する。必要条件を原因と取り違えると漏洩(リーク)になる
DX(DX推進・AI企画) 業務ルールを「必須条件(満たさないと不可)」と「十分条件(満たせば可)」に分けて要件定義書に書く。申請の承認条件の食い違いを防ぐ
RS(研究者) 仮説は反例で否定できるが、例を集めても証明にはならない。反証可能な形に仮説を書く

この授業の目的は、「PならばQ」を見たときに、何を調べれば真偽を判断できるかがわかるようになることです。

前提

次の言葉を使います。不安があれば先に戻ってください。

身近な状況:「割引された人は、みんな会員のはず」と言われた

先ほどのキャンペーン「会員かつ購入額3,000円以上のお客さまは割引」の運用が始まり、6件の注文記録がたまりました。

id 会員か 購入額(円) 割引されたか
1 会員 3,500 された
2 会員 3,000 された
3 会員 2,999 されない
4 非会員 5,000 されない
5 非会員 1,000 された(クーポン利用)
6 会員 1,200 されない

先輩が言いました。「割引されたのは、会員で3,000円以上の人だけだよね。」
後輩が言いました。「でも5番は非会員で1,000円なのに、割引されています。」

2人の主張は、次の2つの文です。

AとBは、AとBの主語と述語を入れ替えただけに見えますが、別の文です。表の6件を見ると、Aは破れていません(会員で3,000円以上の1番・2番は、どちらも割引されています)。一方Bは、5番で破れています。

ポイント:「PならばQ」が正しいことと、「QならばP」が正しいことは、別々に確かめる必要があります。

この授業では、この6件のデータを使って、Aは真、Bは偽であることを、Pythonで確かめます。

直感的な説明:十分条件は「それだけで足りる」、必要条件は「なければ始まらない」

たとえ話で整理します。海外旅行を考えましょう。

必要条件は「これがないとダメ」、十分条件は「これだけで足りる」です。割引のルールで考えると、次のようになります。

条件 割引にとって
会員である 必要条件(会員でなければ、このルールでは割引されない)。ただし、会員なら割引されるとは限らない(2,999円の人がいる)
会員かつ3,000円以上 十分条件(これを満たせば割引される)
購入額が3,000円以上 どちらでもない。非会員の5,000円は割引されない(十分でない)。クーポンでは1,000円でも割引される(必要でもない)

たとえ話の限界:旅行の例は、条件がいくつ必要かを実感するためのものです。実際には「必要」「十分」は、どの範囲のデータで、何についての話かを決めてから使う言葉です。次の節で正確に定義します。

正確な定義:「PならばQ」、反例、逆・裏・対偶

2つの条件 $P$、$Q$ について、「$P$ ならば $Q$」(記号で $P \Rightarrow Q$)を次のように定義します。

$P$ が真で、かつ $Q$ が偽である場合が1つもないとき、$P \Rightarrow Q$ は真である。

真理値表にすると、次のとおりです。

$P$ $Q$ $P \Rightarrow Q$
偽 偽 真
偽 真 真
真 偽 偽(反例)
真 真 真

偽になるのは、「$P$ が真なのに $Q$ が偽」の行だけです。この行を反例と呼びます。$P$ が偽の行は、$Q$ がどうであっても、$P \Rightarrow Q$ を破りません(「会員でない人の割引」は、文Aと無関係です)。論理の言葉では、$P \Rightarrow Q$ は「$(\text{NOT } P)$ OR $Q$」と同じです。

十分条件と必要条件の定義は次のとおりです。

逆・裏・対偶は、元の命題 $P \Rightarrow Q$ から次のように作ります。

名前 形 元の命題と真偽は
逆 $Q \Rightarrow P$ 一致するとは限らない
裏 $\text{NOT } P \Rightarrow \text{NOT } Q$ 一致するとは限らない
対偶 $\text{NOT } Q \Rightarrow \text{NOT } P$ 必ず一致する

対偶が同じ真偽になる理由は、反例の形が同じだからです。$P \Rightarrow Q$ の反例は「$P$ が真で $Q$ が偽」の行で、対偶 $\text{NOT } Q \Rightarrow \text{NOT } P$ の反例は「$\text{NOT } Q$ が真で $\text{NOT } P$ が偽」、つまり同じ行です。

実際に、6件のデータで確かめます。下のコードで、p_rule(r) は「会員かつ3,000円以上」、q_disc(r) は「割引された」を表す関数です(r は1件の記録を表す辞書、戻り値は bool)。find_counter は、反例になる記録の id を集めて、リストで返す関数です。

# 注文記録: id, 会員か, 購入額(円), 割引されたか
records = [
    {'id': 1, 'member': True,  'amount': 3500, 'discounted': True},
    {'id': 2, 'member': True,  'amount': 3000, 'discounted': True},
    {'id': 3, 'member': True,  'amount': 2999, 'discounted': False},
    {'id': 4, 'member': False, 'amount': 5000, 'discounted': False},
    {'id': 5, 'member': False, 'amount': 1000, 'discounted': True},   # クーポン利用
    {'id': 6, 'member': True,  'amount': 1200, 'discounted': False},
]
def p_rule(r):        # P: 会員かつ3000円以上
    return r['member'] and r['amount'] >= 3000
def q_disc(r):        # Q: 割引された
    return r['discounted']

def find_counter(records, p, q):
    # p が真で q が偽の記録の id を返す(反例のリスト)
    return [r['id'] for r in records if p(r) and not q(r)]

print('A (P => Q) の反例:', find_counter(records, p_rule, q_disc))
print('B (Q => P) の反例:', find_counter(records, q_disc, p_rule))

出力は [](反例なし)と [5](5番が反例)です。空のリストは「このデータの範囲では破れていない」という意味であり、「いつでも成り立つ」とは別です。

段階付き例題:必要条件・十分条件・対偶を、データで判定する

問題:前節の6件のデータで、次の3点を判定してください。

  1. 「会員かつ3,000円以上」は、「割引された」の十分条件か。
  2. 「割引された」は、「会員かつ3,000円以上」の必要条件か。
  3. 文Aの対偶は、Aと同じ真偽になるか。

ステップ1:判定の方法を決める。「PならばQ」を調べるには、Pが真の行だけを取り出し、その中にQが偽の行があるかを見ます。1つでもあれば偽です。

ステップ2:1を調べる。Pが真の行は1番・2番で、どちらも割引されています。反例は無く、十分条件です(このデータの範囲で)。

ステップ3:2を調べる。「割引された」が「会員かつ3,000円以上」の必要条件であるとは、Q ⇒ P、つまり割引された人は全員Pを満たすことです。割引された行は1番・2番・5番で、5番はPを満たしません。5番が反例なので、必要条件ではありません。

ステップ4:3を調べる。対偶は「割引されない ⇒ 会員でない、または3,000円未満」です。これを not_q と not_p の関数にして調べます。

# 注文記録: id, 会員か, 購入額(円), 割引されたか
records = [
    {'id': 1, 'member': True,  'amount': 3500, 'discounted': True},
    {'id': 2, 'member': True,  'amount': 3000, 'discounted': True},
    {'id': 3, 'member': True,  'amount': 2999, 'discounted': False},
    {'id': 4, 'member': False, 'amount': 5000, 'discounted': False},
    {'id': 5, 'member': False, 'amount': 1000, 'discounted': True},   # クーポン利用
    {'id': 6, 'member': True,  'amount': 1200, 'discounted': False},
]
def p_rule(r):        # P: 会員かつ3000円以上
    return r['member'] and r['amount'] >= 3000
def q_disc(r):        # Q: 割引された
    return r['discounted']

def find_counter(records, p, q):
    return [r['id'] for r in records if p(r) and not q(r)]

def not_q(r): return not q_disc(r)
def not_p(r): return not p_rule(r)

print('元 P=>Q          :', find_counter(records, p_rule, q_disc))
print('対偶 notQ=>notP   :', find_counter(records, not_q, not_p))
print('逆 Q=>P          :', find_counter(records, q_disc, p_rule))
print('裏 notP=>notQ    :', find_counter(records, not_p, not_q))

ステップ5:結果を読む。元の命題と対偶は、どちらも反例が [] です(必ず一致します)。逆と裏は、どちらも反例が [5] です。逆と裏が一致するのは偶然ではなく、逆の対偶が裏だからです(逆と裏は、互いに対偶の関係にあります)。

ステップ6:仕様の言葉に直す。「このルールを満たせば割引される。ただし、割引されたからといって、このルールを満たしているとは限らない(クーポンなど別の理由がある)」。この文を書けるようになると、仕様書の書き方が変わります。

注意:6件のデータで反例が見つからなかったことは、「Aがいつでも真」の証明ではありません。7件目以降に反例があるかもしれません。反例が1件見つかれば偽と断定できますが、反例が無いことは、確認した範囲の話にとどまります。

操作して理解する:条件を書き換えて、関係の変化を調べる

次のコードは、いくつかの条件の組み合わせを、6件のデータに対して判定します。relation は、2つの条件 p、q の関係を調べる関数です。戻り値の型は文字列(str)です。実行する前に、それぞれ何と表示されるか予想してください。

# 注文記録: id, 会員か, 購入額(円), 割引されたか
records = [
    {'id': 1, 'member': True,  'amount': 3500, 'discounted': True},
    {'id': 2, 'member': True,  'amount': 3000, 'discounted': True},
    {'id': 3, 'member': True,  'amount': 2999, 'discounted': False},
    {'id': 4, 'member': False, 'amount': 5000, 'discounted': False},
    {'id': 5, 'member': False, 'amount': 1000, 'discounted': True},   # クーポン利用
    {'id': 6, 'member': True,  'amount': 1200, 'discounted': False},
]
def p_rule(r):        # P: 会員かつ3000円以上
    return r['member'] and r['amount'] >= 3000
def q_disc(r):        # Q: 割引された
    return r['discounted']

def find_counter(records, p, q):
    return [r['id'] for r in records if p(r) and not q(r)]

def relation(records, p, q):
    a = find_counter(records, p, q) == []   # P => Q が成り立つか
    b = find_counter(records, q, p) == []   # Q => P が成り立つか
    if a and b: return '必要十分'
    if a: return 'pは十分条件'
    if b: return 'pは必要条件'
    return 'どちらでもない'

def is_member(r): return r['member']
def big(r): return r['amount'] >= 3000

print('会員かつ3000円以上 → 割引 :', relation(records, p_rule, q_disc))
print('会員である → 割引         :', relation(records, is_member, q_disc))
print('3000円以上 → 割引         :', relation(records, big, q_disc))

予想と答え:

次に、データを書き換えます。

  1. 5番の記録(クーポン)を records から削除すると、p_rule と q_disc の関係はどうなりますか。
  2. 5番を削除したうえで、4番(非会員の5,000円)の discounted を True に変えると、「会員である」は割引の必要条件になりますか。

答えは、(1) 必要十分 になる(残る5件では、割引された人と条件を満たす人が完全に一致する)、(2) ならない。4番が非会員なのに割引されているので、「割引された ⇒ 会員」の反例になります。データが変わると関係も変わることを確認してください。

発展:反例の探し方・空虚な真・「いつでも」の証明

空虚な真:$P$ が真になる行が1件もないとき、$P \Rightarrow Q$ は「反例が無い」ので真と判定されます。たとえば「購入額が100万円以上ならば割引」は、100万円以上の注文が1件も無いデータでは、反例が無く真になります。このような場合の真は、実質的な情報を持ちません。find_counter の結果が [] のときは、$P$ が真の行が何件あったかも併せて報告すると、判断を誤りません。

例を集めても証明にならない:$P \Rightarrow Q$ を確かめた例($P$ も $Q$ も真の行)が100万件あっても、反例が1件あれば偽です。逆に、反例が見つからないことを「いつでも成り立つ」と言うには、全件を調べるか、理由を示す(定義から導く)必要があります。全件を調べる費用は 計算量と小さい反例 で扱います。

同値でも「原因」とは限らない:$P \Leftrightarrow Q$ が成り立つことは、$P$ が $Q$ の原因であることを意味しません。たとえば、解約した人だけに入力される「解約理由コード」は、「解約した」と必要十分の関係に見えますが、解約の結果であり、予測に使うと漏洩(リーク)になります。必要十分は論理の関係、原因は別の話です。

代替手段:条件が多いときは、条件を1行ずつ表にし、「必須」「十分」「参考」の列に○を付けるチェックリストにすると、関係者との合意が取りやすくなります。表の書き方は、ON06 の総合テストのあとに取り組む統合課題(会員割引の条件を表で検証する課題)の準備にもなります。

よくある失敗

失敗1:逆を同じ意味だと思い込む

誤った結論:「割引されたのだから、この人は会員で3,000円以上のはずだ。」

# 注文記録: id, 会員か, 購入額(円), 割引されたか
records = [
    {'id': 1, 'member': True,  'amount': 3500, 'discounted': True},
    {'id': 2, 'member': True,  'amount': 3000, 'discounted': True},
    {'id': 3, 'member': True,  'amount': 2999, 'discounted': False},
    {'id': 4, 'member': False, 'amount': 5000, 'discounted': False},
    {'id': 5, 'member': False, 'amount': 1000, 'discounted': True},   # クーポン利用
    {'id': 6, 'member': True,  'amount': 1200, 'discounted': False},
]
def p_rule(r):        # P: 会員かつ3000円以上
    return r['member'] and r['amount'] >= 3000
def q_disc(r):        # Q: 割引された
    return r['discounted']

discounted = [r for r in records if r['discounted']]
for r in discounted:
    print(r['id'], '会員かつ3000円以上か →', p_rule(r))

割引された3件のうち、5番は False です。5番の割引は、クーポンという別の理由によるものでした。「Pならば割引」が真でも、「割引ならばP」は真と限りません。

直し方:逆の向きの主張をするときは、その向きの反例を別に探す。この例では、find_counter(records, q_disc, p_rule) を実行すれば、5番がすぐに見つかります。

失敗2:必要条件と十分条件の向きを取り違える

誤った文:「3,000円以上であることは、割引の十分条件だ。3,000円以上なら割引されるから。」

# 注文記録: id, 会員か, 購入額(円), 割引されたか
records = [
    {'id': 1, 'member': True,  'amount': 3500, 'discounted': True},
    {'id': 2, 'member': True,  'amount': 3000, 'discounted': True},
    {'id': 3, 'member': True,  'amount': 2999, 'discounted': False},
    {'id': 4, 'member': False, 'amount': 5000, 'discounted': False},
    {'id': 5, 'member': False, 'amount': 1000, 'discounted': True},   # クーポン利用
    {'id': 6, 'member': True,  'amount': 1200, 'discounted': False},
]
def p_rule(r):        # P: 会員かつ3000円以上
    return r['member'] and r['amount'] >= 3000
def q_disc(r):        # Q: 割引された
    return r['discounted']

big = [r for r in records if r['amount'] >= 3000]
for r in big:
    print(r['id'], '会員:', r['member'], '割引:', r['discounted'])

3,000円以上の3件のうち、4番(非会員の5,000円)は割引されていません。3,000円以上であっても、割引にならない場合があるので、十分条件ではありません。なお、このルールで割引されるには、金額が3,000円以上であることは必要です(ルールの範囲では)。ただし、クーポンという別の経路があるので、データ全体では必要でもありません。

直し方:十分か必要かを判断するときは、「$P$ が真の行のうち、$Q$ が偽の行はあるか(十分条件の反例)」と、「$Q$ が真の行のうち、$P$ が偽の行はあるか(必要条件の反例)」を別々に調べる。

失敗3:確認できた例を数えて「正しい」と結論する

誤ったやり方:Aが成り立つ例を数え、数が多いから正しいとする。逆Bについても同じ数え方をしてみます。

# 注文記録: id, 会員か, 購入額(円), 割引されたか
records = [
    {'id': 1, 'member': True,  'amount': 3500, 'discounted': True},
    {'id': 2, 'member': True,  'amount': 3000, 'discounted': True},
    {'id': 3, 'member': True,  'amount': 2999, 'discounted': False},
    {'id': 4, 'member': False, 'amount': 5000, 'discounted': False},
    {'id': 5, 'member': False, 'amount': 1000, 'discounted': True},   # クーポン利用
    {'id': 6, 'member': True,  'amount': 1200, 'discounted': False},
]
def p_rule(r):        # P: 会員かつ3000円以上
    return r['member'] and r['amount'] >= 3000
def q_disc(r):        # Q: 割引された
    return r['discounted']

confirm = [r['id'] for r in records if r['discounted'] and p_rule(r)]
counter = [r['id'] for r in records if r['discounted'] and not p_rule(r)]
print('Bに合う例(割引かつP):', confirm, len(confirm), '件')
print('Bの反例(割引だがPでない):', counter, len(counter), '件')

Bに合う例は2件、反例は1件です。「2件対1件だから、Bはおおむね正しい」と言いたくなりますが、反例が1件あれば、「割引されたならば必ずP」という主張は偽です。「たいてい成り立つ」と「必ず成り立つ」は、別の主張です。

直し方:「必ず」と言いたいなら反例を探す。「たいてい」と言いたいなら、割合(この例では3件中2件)として、そう書く。

まとめ

用語

必要条件(necessary condition)

「PならばQ」が真のときのQ。Pが成り立つために欠かせない条件。

十分条件(sufficient condition)

「PならばQ」が真のときのP。それだけでQが成り立つと言える条件。

必要十分条件(necessary and sufficient condition)

PならばQ、QならばPの両方が真であること。PとQの真偽が常に一致する。

反例(counterexample)

「PならばQ」を偽にする、Pが真でQが偽の例。1件で命題を否定できる。

対偶(contrapositive)

「PならばQ」に対する「QでないならばPでない」。元の命題と必ず同じ真偽になる。

よくある質問

なぜ「必要条件」「十分条件」という言葉を分けて使うのですか?

仕様の誤りの多くが、「これを満たせば足りる」と「これを満たさないと始まらない」の取り違えから起きるからです。たとえば、申請の「必須書類が揃っている」は必要条件ですが、揃っていれば承認されるわけではありません。2つを分けて書くと、抜けている条件が見えます。

「必要十分条件」は、日常語の「ちょうどそのとき」と考えてよいですか?

おおむねその感覚でかまいません。正確には、PとQの真偽が全件で一致していることです。データでは、PとQが食い違う行が1件も無いと確認できたときに、「この範囲で必要十分」と言えます。ただし、データの範囲外で成り立つ保証ではありません。

対偶は、何の役に立つのですか?

元の命題を直接調べにくいときに、同じ真偽の別の形で調べられます。「割引されたら会員」を調べるより、「非会員ならば割引されない」を調べるほうが簡単な場面があります。データの確認でも、反例の探し方を変えられるのが利点です。

実務で「逆が成り立たない」ことは、どんな場面で問題になりますか?

たとえば、「解約した人は、解約前に問い合わせが多かった」という分析結果から、「問い合わせが多い人は解約する」と施策を打つ場合です。逆向きは別に確かめる必要があり、問い合わせが多くても解約しない人が多数なら、施策は空振りします。

データに反例が見つからなければ、仕様として「PならばQ」と書いてよいですか?

書いてよいのは、「確認した範囲では成り立つ」までです。仕様に「必ず」と書くなら、ルールの定義から成り立つ理由を説明するか、全件・境界値を網羅して確かめる必要があります。期間・対象・件数を、確認した条件として併記してください。

「PならばQ」は、PがQの原因という意味ですか?

違います。論理の「ならば」は、真偽の関係だけを表します。「雨が降ったなら地面が濡れている」は成り立っても、濡れた地面が雨の原因ではありません。原因を言いたいときは、実験や設計の工夫が別に必要です(相関と因果は区別します)。

確認問題

問1 「P ならば Q」が真のとき、Q は P の何と呼ばれるか。

  1. ① 十分条件
  2. ② 必要条件
  3. ③ 必要十分条件
  4. ④ 対偶
解答と解説

正解:②

P ⇒ Q が真のとき、P は Q の十分条件、Q は P の必要条件です。Qが成り立たなければPも成り立たないからです。

問2 命題「P ならば Q」を偽だと示すには、どのデータが1件あればよいか。

  1. ① P が偽で Q が真の記録
  2. ② P が真で Q が偽の記録
  3. ③ P も Q も偽の記録
  4. ④ P も Q も真の記録
解答と解説

正解:②

P ⇒ Q が破れるのは、P が真なのに Q が偽の場合だけです。この1件が反例です。

問3 「会員かつ3,000円以上ならば割引」の対偶はどれか。

  1. ① 割引されたならば、会員かつ3,000円以上
  2. ② 会員でないならば、割引されない
  3. ③ 割引されないならば、会員かつ3,000円未満
  4. ④ 割引されないならば、会員でない、または3,000円未満
解答と解説

正解:④

対偶は NOT Q ⇒ NOT P です。NOT P は、ド・モルガンの法則により「会員でない、または3,000円未満」になります。

問4 データで「割引された人」を調べると、全員が会員だった。この事実から、安全に言えるのはどれか。

  1. ① 会員であれば必ず割引される
  2. ② 割引は会員だけの特典である(それ以外に割引される人は将来も出ない)
  3. ③ このデータの範囲では、会員は割引の必要条件に反しない(割引された人は会員だった)
  4. ④ 会員であることが割引の原因である
解答と解説

正解:③

「割引された ⇒ 会員」の反例が、このデータには無いというだけです。十分条件でも原因でも、将来の保証でもありません。

問5 P が偽のとき、命題「P ならば Q」の真偽はどうなるか。

  1. ① Q に関係なく偽
  2. ② Q が偽のときだけ真
  3. ③ Q に関係なく真
  4. ④ 判定できない
解答と解説

正解:③

P ⇒ Q が偽になるのは、P が真で Q が偽の場合だけです。P が偽なら反例にならず、真と扱います。

問6 ルール「会員かつ3,000円以上ならば割引」で、2,999円の会員は割引されない。この事実から、正しい説明はどれか。

  1. ① 会員は割引の十分条件である
  2. ② 会員は割引の必要条件だが、十分条件ではない
  3. ③ 会員は割引の必要十分条件である
  4. ④ 会員は割引と無関係である
解答と解説

正解:②

割引には会員であることが欠かせない(必要)が、会員だけでは割引にならない(2,999円の会員が反例)ので、十分条件ではありません。

次に学ぶこと

次は 手順・分岐・繰り返し で、条件を手順の中に置く方法(分岐と繰り返し)を学びます。この授業の「反例を探す」は、手順を追跡して条件の漏れを見つける作業にそのまま使います。

if文の書き方は 「ifと条件式」(有料版)、境界値の検証は 「境界ケースの検証」(有料版) でも扱います。この授業の確認問題だけでモジュールの習得とはしません。ON06 の総合テストと、会員割引の条件を表で検証する自力課題で判定します。

キャリア学習:データサイエンティスト・機械学習エンジニア・DX推進・研究者を目指す人の学習アプリ。164モジュール・984授業を、ブラウザで動くSQLとPythonの演習で。基礎と各分野の最初のモジュールは無料。全部は2,980円の買い切り。

アプリを無料で開く有料版と無料版の違い