課題の解説 python 中級編

課題の確認¶

目標課題 以下の車の製品データと修理履歴データを分析し、

車の種類別に異常Aが初めて起きたときn年以内に異常Bが発生する確率を求めて出力するプログラムを作成しよう。

課題の細分化¶

このような課題を解くときは問題を細分化して設計を固めてから作り出すのが良いだろう。

今回はステップを以下のように分けることにした。

Step 1. 関数load_product_csvの作成 (製品.csvの読み込み)

Step 2. 関数load_repair_csvの作成 (修理.csvの読み込み)

Step 3. 関数make_product_repair_dictの作成 (製品ごとに起きた修理イベントを格納)

Step 4. 関数date_diffの作成(日時の間に何年の差があるかを計算)

Step 5. 関数split_by_first_problemの作成(異常が初めて起きた時の修理イベント情報を抽出)

Step 6. 関数problem_in_timeの作成(時間内に特定の異常が起きているか検出)

Step 7. 関数calculate_probabilityの作成(ある異常が起きた時n年以内にある異常が起こる確率を計算)

メイン関数の作成

以下の章で詳しく説明するので、始めていこう!

import csv
# 1. 製品.csvを読み込み辞書を作る  # 製品の車種と製品番号を全部1回ずつとるために必要
# product_dict = {"車種A_108": "2002 02" ... }のような形式となる 
def load_product_csv():
    product_dict = {}
    with open('製品.csv', 'r',encoding="utf-8") as f:
        csv_reader = csv.reader(f)

        # csvファイルのヘッダーをスキップ
        next(csv_reader)
        for row in csv_reader:
            # rowの構造は製品.csvのファイルの中身をみてみよう。
            # 製品固有のID、product_idを抽出。
            product_id = row[0]
            # 要素として生産日時をいれる(文字列のまま)
            date = row[1]
            product_dict[product_id] = date
    return product_dict
# 2. 修理.csvを読み込み、修理情報のリストを作る
# repair_list = [["車種A_108", "2011 04", "エンジンの異常", "1"], [...] ...]
def load_repair_csv():
    # データを入れる用の空リストrepair_listを作成する 
    repair_list = []
    with open('修理.csv', 'r',encoding="utf-8") as f:
        csv_reader = csv.reader(f)
        # csvファイルのヘッダーをスキップ 
        next(csv_reader) これでヘッダーをスキップ
        # データの読み込み(repair_listに格納)
        for i in csv_reader:
            repair_list.append(i)
    return repair_list
# 3. productとrepair_listから、製品ごとに起きた修理イベントの情報を格納する。
# 製品ID(車種, 製品番号)を辞書のキーとし、修理イベントを格納した辞書のリストを要素とする。
# 修理イベントは時系列順にソートされている。
def make_product_repair_dict(product_dict, repair_list):
    # 辞書の内包表記を利用してproduct_dictのキーと同じキーで空のリストを要素とする辞書を作る
    product_repair_dict = { i:[] for i in product_dict.keys()}
    for repair in repair_list:
        info = {}
        # 辞書のキーであるproduct_idを求める
        product_id = repair[0]
        info["日時"] = repair[1]
        info["異常"] = repair[2]
        info["破棄"] = repair[3]
        # product_repair_dictのにproduct_idの要素にinfoをappendしていく
        product_repair_dict[product_id].append(info)

    return product_repair_dict
# step4. date1, date2を引数としてdate1からdate2までどのくらいの時間が経ったのかを計算し、返す関数を作る。
def date_diff(date0, date1):
    # 文字列情報から年、月のint型情報を抽出
    # ヒント1: dateは"2011 03"のように文字列型なので計算のためにはsplitと型変換が必要
    # ヒント2: 1行で年、月を同時に求めるにはリストの内包表記を使うと良い。 
    year0, month0 = date0.split(" ")
    year1, month1 = date1.split(" ")
    # 年、月のint型情報から時間差を年単位で計算
    diff = (int(year1)+int(month1)/12)-(int(year0)+int(month0)/12)
    return diff
# step5. 異常problemが起こった場合は初めて起きた時からの修理情報とその前の修理情報で分ける関数を作る
# 異常problemが起きていない場合は2つ目の返り値として空リスト[]を返す。
# ある製品の修理履歴リストrepair_dict_listと異常problemを引数と取る。
# 修理履歴リスト: repair_dict_list = [{"日時": "2011 04", "異常": "エンジンの異常","破棄": "1"}, {...}, ...]
def split_by_first_problem(repair_dict_list, problem):
    for i, repair_dict in enumerate(repair_dict_list):
        # 異常problemが起きたら初めて起きた時からの修理情報とその前の修理情報でスライスしたリストを返す
        if problem == repair_dict["異常"]:
            return repair_dict_list[:i],repair_dict_list[i:]
         この表記でリストを分割できる。
    # 異常problemが起きていない場合には1個目はrepair_dict_list、2個目にはからのリストを返す
    return repair_dict_list, []
# step6. step5の関数から受け取ったrepair_dict_list[i:]を引数と取り、初めての事故よりtime年内にproblemが起きるか調べる
# 起きてたらTrue, 起きてなかったらFalseを返す関数を作る。
def problem_in_time(repair_dict_list, problem, time):
    # 基準となる時間を抽出しておく
    date0 = repair_dict_list[0]["日時"]
    for repair_dict in repair_dict_list[1:]:
        date1 = repair_dict['日時']
        # 修理履歴は時系列順にソートされているので時間差がtime年を超えるとループを終わらせる。
        # ヒント:date_diff関数を利用する。
        if date_diff(date0, date1) > time:
            break
        # 時間内にproblemが起きていた場合Trueを返す
        elif problem == repair_dict["異常"]:
            return True
    # repair_dict_list = [] の場合やproblemがtime年以内に起きていない場合はここでFalseを返す
    return False
# 7. product_repair_dictから種類car_classの車で異常problem0が初めて起きたとき異常problem1がtime年以内に起きる確率を求めよう
# ヒント:car_classが異なるものはfor文ないでcontinueなどで飛ばせば良い。

def calculate_probability(all_product_repair_dict, car_class, time, problem0, problem1):
    total_count = 0
    target_count = 0

    for product_id in all_product_repair_dict:
        repair_dict_list = all_product_repair_dict[product_id]
        # 車種がcarclassでない場合次のループにスキップ
        if car_class not in product_id: 
       product_idの中身が”車種A_75”みたいな形なので、car_classそのものではないので、必ず not in の形をとる。
            continue
        # 初めてproblem0が起きた時からの修理履歴をstep5の関数を用いて抽出
        _, repair_dict_list1 = split_by_first_problem(repair_dict_list, problem0)
        # repair_dict_list1が空でない場合total_countでカウントする。
        if repair_dict_list1: 空でないは!=[]ではない。
            total_count += 1
            # time年以内にproblem1が起きていたらtarget_countでカウントする。
            # ヒント:step6の関数を使って判断する
            if problem_in_time(repair_dict_list1, problem1, time):
                target_count += 1
    return (target_count / total_count) * 100

# main文実行
def main():
    product_dict = load_product_csv()
    repair_list = load_repair_csv()
    product_repair_dict = make_product_repair_dict(product_dict, repair_list)
    kuruma = "車種A"
    time = 5
    problemA = "エンジンの異常"
    problemB = "冷却系の異常"
    problemC = "バッテリの異常"
    rate = calculate_probability(product_repair_dict, kuruma, time, problemB, problemA)
    print("車種Aの{}が初めて起きたとき、5年以内に{}が起きる確率は{:.2f}%である。".format(problemB, problemA, rate))
    rate = calculate_probability(product_repair_dict, kuruma, time, problemC, problemA)
    print("車種Aの{}が初めて起きたとき、5年以内に{}が起きる確率は{:.2f}%である。".format(problemC, problemA, rate))
    rate = calculate_probability(product_repair_dict, kuruma, time, problemA, problemA)
    print("車種Aの{}が初めて起きたとき、5年以内に{}が起きる確率は{:.2f}%である。".format(problemA, problemA, rate))

main()
車種Aの冷却系の異常が初めて起きたとき、5年以内にエンジンの異常が起きる確率は4.66%である。
車種Aのバッテリの異常が初めて起きたとき、5年以内にエンジンの異常が起きる確率は1.60%である。
車種Aのエンジンの異常が初めて起きたとき、5年以内にエンジンの異常が起きる確率は15.86%である。

コメント

タイトルとURLをコピーしました