⇧ 映画とかでしか見たこと無いけど「干し草」を丸めたやつって「ロールベール」ってのが正規名称だったんですな、それにしても重量が「1トン」超えるの!
衝撃が冷めやらず、ザワザワしちゃってます、どうもボクです。
By the way、「教師なし機械学習」の1つ「K-means」で、「機械学習モデルの性能評価」のためには「正解データ」が必要なことに、最近気づいた情弱な私です...。
そして、遂に2020年のGW、冒頭で書いた2冊の本を同時並行的に読んでいる中で、答えにたどり着くことができました。「Outlier Analysis」のP.30の「1.7.2 Common Mistakes in Benchmarking」に答えが書いてあります。より正確にはこのブログだけでなく原書をぜひ読んでみてください(※1)。
要するに、答えは
「できない」
でした(汗。
教師なし学習の性能評価は雰囲気でやるしかない - WAF Tech Blog | クラウド型 WAFサービス Scutum 【スキュータム】
⇧ 上記サイト様の記事で、確信に変わった次第です。
そうなんですよ、確かに「教師なし機械学習」で作成する「機械学習モデル」の「学習」には「答え」が必要ないんですけど、「性能評価」の時に「答え」が必要になってくるってのがね、盲点でしたですね。
そんなわけで、「モデルの性能評価」はできてませんが、
⇧ 「教師なし機械学習」の1つ「K-means」を実施してみました。Google Colaboratryで動くかと~。
ただ、「K-means」使った意味が無かったので、「K-means」が活かせる方法を模索していきたいところです。
2021年4月13日(火)追記:↓ ここから
なんか、
⇧ ってことみたいなので、要注意ですね。自分も違い分かってなかったっす...
2021年4月13日(火)追記:↑ ここまで
そんでは、今回は、「Tidy Data(整然データ)」について調べてみました。
レッツトライ~。
Tidy Data(整然データ)って何?
Pythonのライブラリである「Pandas」の公式のCheet Sheetで、
⇧「Tidy Data」って言葉が出てくるのですが、What’s Tidy Data?って思ったので調べてみました。
⇧ 上記サイト様が分かりやすいです。
上記サイト様で紹介のあったサイトに「整然データ」の説明があります。
概要
整然データとは、1) 個々の変数が1つの列をなす、2) 個々の観測が1つの行をなす、3) 個々の観測の構成単位の類型が1つの表をなす、4) 個々の値が1つのセルをなす、という4つの条件を満たした表型のデータのことであり、構造と意味が合致するという特徴を持つ。R言語などを用いたデータ分析の際には非常に有用な概念である。
⇧ ってな感じで、
- 個々の変数が1つの列をなす
- 個々の観測が1つの行をなす
- 個々の観測の構成単位の類型が1つの表をなす
- 個々の値が1つのセルをなす
の4つの条件を満たす表型のデータを「Tidy Data(整然データ)」 というらしい。
Wikipediaができてたみたい。
整然データ (せいぜんデータ、英: Tidy data) は、 次の4つの条件を満たした表型データのこと。
- 個々の変数が1つの列をなす
- 個々の観測が1つの行をなす
- 個々の観測の構成単位の類型が1つの表をなす
- 個々の値が1つのセルをなす
構造と意味が合致するという特徴を持ち、R言語などを用いたデータ分析の際には非常に有用な概念である。
「Pandas」の「melt()」メソッドで「Tidy Data(整然データ)」にできるらしい
データ分析するなら「整然データ」が良いらしいということで、Pythonを使ってる場合は「Pandas」に用意されてる「melt()」 メソッドを使うと「Tidy Data(整然データ)」に変換できるようです。
⇧「Pandas」公式のCheet Sheetの「Reshaping Data - Change the layout of a data set」の中で紹介されていますね。
「Pandas」の「melt()」 メソッドについての「API Refernce」は以下ですね。
メソッド実効後のイメージ図は以下ですね。
⇧「melt()」 メソッドで、どうして、こういう形に変形するのかってのがいまいち腑に落ちてなかったのですが、「Tidy Data(整然データ)」っていう考え方を前提にしてたってことなんですかね。
「カテゴリ変数(質的データ)」を「ダミー変数化(one-hot表現)」後に「Tidy Data(整然データ)」にする?
「Tidy Data(整然データ)」 にするべきってことは分かったのですが、ここで今一度、データサイエンスで扱う「データ」がどうなっているのが望ましいのかの原点に戻ってみますか。
実際にプログラミングで「データ」を用意すると、「データ型」によって「データ」の値は様々になってきますと。
Pythonは「動的型付け言語」なので「データ型」が曖昧になってしまっている部分はありますが、ExcelファイルとかCSVファイルとかから「データ」が用意できるわけですが、それぞれのファイルの値をPythonで読み込むと、値が
- int型
- float型
- String型
- Object型
などなど、混在することは多々ありそうですと。
で、「機械学習」では、機械学習モデルを 作成するために「データ」を読み込んで学習させるわけですが、学習に使われる「データ」の値は「数値」である必要がありますと。
何故「数値」である必要があるのか?
⇧ 上記のPDFで述べられてる通り、つまるところ、「機械学習(ML:Machine Learning)」というのは「入力(数値)」を受け取って適切な「出力(数値)」に変換してくれる「関数」を見つけることであるからして、「入力」は「数値」にしておいてあげないと、上手いこと計算とかできませんと。
というわけで「データ」は「数値」にしてあげましょう、なんですが、人間社会で扱われてる「データ」の値としては、当然のことながら「数値」以外も存在するんですと。
ちなみに、「統計」においては「データ」は、
⇧ みたいなものに分類できて、この「質的データ」ってものを「数値」にしておく必要がありますと。
で、「Pandas」を使ってる場合、「get_dummy()」メソッドってのが用意されてるので「質的データ」を「ダミー変数」にお手軽に変換できますと。
pandas.get_dummiesとsklearn.preprocessing.OneHotEncoderにはそういう違いがあるんですね。
「ダミー変数」「One-Hotエンコーディング」という言葉自体、中身のやることは同じという認識でよろしいのでしょうか?
私の中では同じという認識です。
⇧ とあるので、「One-Hotエンコーディング」を確認してみると、
デジタル回路において、one-hot(ワン・ホット)は1つだけHigh(1)であり、他はLow(0)であるようなビット列のことを指す。 類似のものとして、0が1つだけで、他がすべて1であるようなビット列をone-cold(ワン・コールド)と呼ぶことがある。
one-hotエンコーディングはしばしば状態機械の状態を表すのに用いられる。二進法やグレイコードを使うときにはその状態を決定するためにデコードが必要だが、one-hotを使うときには不要である。というのも、one-hotではビット列のn番目のビットがHighであればn番目の状態を表していることになるからである。
⇧ みたいな感じで、「0」か「1」で表現するってことみたい。
なので、例えば「データ」に『血液型』っていう列があったとすると、
ID | 血液型 |
---|---|
0 | A |
1 | B |
2 | AB |
3 | O |
4 | A |
5 | A |
「Pandas」のget_dummy()メソッドを使うと、
ID | 血液型_A | 血液型_B | 血液型_AB | 血液型_O |
---|---|---|---|---|
0 | 1 | 0 | 0 | 0 |
1 | 0 | 1 | 0 | 0 |
2 | 0 | 0 | 1 | 0 |
3 | 0 | 0 | 0 | 1 |
4 | 1 | 0 | 0 | 0 |
5 | 1 | 0 | 0 | 0 |
みたいな形で、「質的データ」を「数値」として表現できますと。
ただ、 「ダミー変数」にする際は、「列」を1つ減らすのが、「データサイエンス」の世界ではお決まりということなので、
get_dummy(df["血液型"], drop_first=True))
⇧ みたいな感じで、「drop_first=True」を指定してあげるのが普通らしいです。
なので、『血液型』の例は、
ID | 血液型 |
---|---|
0 | A |
1 | B |
2 | AB |
3 | O |
4 | A |
5 | A |
「Pandas」の「get_dummy()」メソッドで「drop_first=True」を指定して実行することになり、
ID | 血液型_B | 血液型_AB | 血液型_O |
---|---|---|---|
0 | 0 | 0 | 0 |
1 | 1 | 0 | 0 |
2 | 0 | 1 | 0 |
3 | 0 | 0 | 1 |
4 | 0 | 0 | 0 |
5 | 0 | 0 | 0 |
上記のように表現するみたいね。
「血液型_A」については、「血液型_B」「血液型_AB」「血液型_O」が いずれも「0」であることによって「血液型_A」が「1」になるということを表すみたいです。
で、「ダミー変数」への変換が済んだ後に、「整然データ」にしていくという流れですかね。
ただ、「pandas.get_dummies」については、
⇧ 上記サイト様によりますと、「使ってはいけない」と言ってるので、実際の現場では、禁じ手的な扱いになるんですかね?
まぁ、今回は「pandas.get_dummies」使ってきますけど...
というわけで、「Tidy Data(整然データ)」にしてみる
まずは「データ」を用意ということで、
⇧ 上記サイト様を参考に、無料で利用できる「データ」を使ってみようと思いましたが、サイトのリンクとか切れてたりしたこともあり、自分でデータを用意しました。
「七つの大罪 - Wikipedia」の情報を参考に「データ」を用意。
df = pd.DataFrame({ "iD": [1, 2, 3, 4, 5, 6, 7], "name": ["メリオダス", "バン", "ディアンヌ", "キング/ハーレクイン", "ゴウセル", "マーリン", "エスカノール"], "nickName": ["憤怒の罪(ドラゴン・シン)", "強欲の罪(フォックス・シン)", "嫉妬の罪(サーペント・シン)", "怠惰の罪(グリズリー・シン)", "色欲の罪(ゴート・シン)", "暴食の罪(ボア・シン)", "傲慢の罪(ライオン・シン)"], "species": ["魔神族", "元人間", "巨人族", "妖精族", "人形", "不明", "人間"], "power": ['{"magic": 400, "force", 990, "spirit": 2010 }', '{"magic": 1380, "force", 930, "spirit": 910 }', '{"magic": 900, "force", 1970, "spirit": 480 }', '{"magic": 3370, "force", 0, "spirit": 820 }', '{"magic": 1300, "force", 500, "spirit": 1300 }', '{"magic": 3540, "force", 70, "spirit": 1100 }', '{"magic": 5, "force", 5, "spirit": 5 }'], "height": ["152", "210", "915", "160", "173", "177", "165"], "weight": ["50", "70", "不明", "48", "61", "57", "49"], "age": ["3000", "43", "750", "1300", "不明", "不明", "40"], "blood": ["B", "B", "O", "AB", "不明", "AB", "AB"] })
って「データ」があったとして、まずは、「数値」でない「データ」を何とかせにゃならんので、どうにかして「カテゴリ変数」に変換せねばなのですが、
⇧ 上記サイト様によりますと、「Category Encoders」ってのが便利らしいんですが、「データ」の値が「JSON文字列」とかでも良しなに処理してくれるんじゃろうか?
やってみた。(事前に、pipで「Numpy」「Pandas」「Category Encoders」とかはPython仮想環境にインストールしてます。)
import pandas as pd import numpy as np import category_encoders as ce df = pd.DataFrame({ "iD": [1, 2, 3, 4, 5, 6, 7], "name": ["メリオダス", "バン", "ディアンヌ", "キング/ハーレクイン", "ゴウセル", "マーリン", "エスカノール"], "nickName": ["憤怒の罪(ドラゴン・シン)", "強欲の罪(フォックス・シン)", "嫉妬の罪(サーペント・シン)", "怠惰の罪(グリズリー・シン)", "色欲の罪(ゴート・シン)", "暴食の罪(ボア・シン)", "傲慢の罪(ライオン・シン)"], "species": ["魔神族", "元人間", "巨人族", "妖精族", "人形", "不明", "人間"], "power": ['{"magic": 400, "force", 990, "spirit": 2010 }', '{"magic": 1380, "force", 930, "spirit": 910 }', '{"magic": 900, "force", 1970, "spirit": 480 }', '{"magic": 3370, "force", 0, "spirit": 820 }', '{"magic": 1300, "force", 500, "spirit": 1300 }', '{"magic": 3540, "force", 70, "spirit": 1100 }', '{"magic": 5, "force", 5, "spirit": 5 }'], "height": ["152", "210", "915", "160", "173", "177", "165"], "weight": ["50", "70", "不明", "48", "61", "57", "49"], "age": ["3000", "43", "750", "1300", "不明", "不明", "40"], "blood": ["B", "B", "O", "AB", "不明", "AB", "AB"] }) exclude_list = ["height", "weight", "age"] list_cols = [x for x in df.columns if x not in exclude_list] cols = ["power"] ce_ohe = ce.OneHotEncoder(cols=cols,handle_unknown='impute') df_ce = ce_ohe.fit_transform(df) print("■変更前") print(df) print("■変更後") print(df_ce)
実行結果は、
■変更前 iD name nickName species power height weight age blood 0 1 メリオダス 憤怒の罪(ドラゴン・シン) 魔神族 {"magic": 400, "force", 990, "spirit": 2010 } 152 50 3000 B 1 2 バン 強欲の罪(フォックス・シン) 元人間 {"magic": 1380, "force", 930, "spirit": 910 } 210 70 43 B 2 3 ディアンヌ 嫉妬の罪(サーペント・シン) 巨人族 {"magic": 900, "force", 1970, "spirit": 480 } 915 不明 750 O 3 4 キング/ハーレクイン 怠惰の罪(グリズリー・シン) 妖精族 {"magic": 3370, "force", 0, "spirit": 820 } 160 48 1300 AB 4 5 ゴウセル 色欲の罪(ゴート・シン) 人形 {"magic": 1300, "force", 500, "spirit": 1300 } 173 61 不明 不明 5 6 マーリン 暴食の罪(ボア・シン) 不明 {"magic": 3540, "force", 70, "spirit": 1100 } 177 57 不明 AB 6 7 エスカノール 傲慢の罪(ライオン・シン) 人間 {"magic": 5, "force", 5, "spirit": 5 } 165 49 40 AB ■変更後 iD name nickName species power_1 power_2 power_3 power_4 power_5 power_6 power_7 height weight age blood 0 1 メリオダス 憤怒の罪(ドラゴン・シン) 魔神族 1 0 0 0 0 0 0 152 50 3000 B 1 2 バン 強欲の罪(フォックス・シン) 元人間 0 1 0 0 0 0 0 210 70 43 B 2 3 ディアンヌ 嫉妬の罪(サーペント・シン) 巨人族 0 0 1 0 0 0 0 915 不明 750 O 3 4 キング/ハーレクイン 怠惰の罪(グリズリー・シン) 妖精族 0 0 0 1 0 0 0 160 48 1300 AB 4 5 ゴウセル 色欲の罪(ゴート・シン) 人形 0 0 0 0 1 0 0 173 61 不明 不明 5 6 マーリン 暴食の罪(ボア・シン) 不明 0 0 0 0 0 1 0 177 57 不明 AB 6 7 エスカノール 傲慢の罪(ライオン・シン) 人間 0 0 0 0 0 0 1 165 49 40 AB
駄目でした...
う~ん、やっぱり泥臭く人手で処理せにゃならんところは人手で処理してあげにゃならんのかね、「データ」の「前処理」は地獄ですな...
なので、「JSON文字列」とかは、一旦、自力で「JSON」の形(Pythonだと「dict」っていう型になるらしい)にしてあげる必要がありますと。
というか、値が「文字列」の「データ」なんかについては、「カテゴリ変数」に変換する前に「自然言語処理」とかしてあげておくと、「機械学習モデル」 の性能が上がるっぽいです。
話が脱線しましたが、以下でイケました。
import pandas as pd import numpy as np import category_encoders as ce import json import ast df = pd.DataFrame({ "iD": [1, 2, 3, 4, 5, 6, 7], "name": ["メリオダス", "バン", "ディアンヌ", "キング/ハーレクイン", "ゴウセル", "マーリン", "エスカノール"], "nickName": ["憤怒の罪(ドラゴン・シン)", "強欲の罪(フォックス・シン)", "嫉妬の罪(サーペント・シン)", "怠惰の罪(グリズリー・シン)", "色欲の罪(ゴート・シン)", "暴食の罪(ボア・シン)", "傲慢の罪(ライオン・シン)"], "species": ["魔神族", "元人間", "巨人族", "妖精族", "人形", "不明", "人間"], "power": ['{"magic": 400, "force": 990, "spirit": 2010 }', '{"magic": 1380, "force": 930, "spirit": 910 }', '{"magic": 900, "force": 1970, "spirit": 480 }', '{"magic": 3370, "force": 0, "spirit": 820 }', '{"magic": 1300, "force": 500, "spirit": 1300 }', '{"magic": 3540, "force": 70, "spirit": 1100 }', '{"magic": 5, "force": 5, "spirit": 5 }'], "height": ["152", "210", "915", "160", "173", "177", "165"], "weight": ["50", "70", "不明", "48", "61", "57", "49"], "age": ["3000", "43", "750", "1300", "不明", "不明", "40"], "blood": ["B", "B", "O", "AB", "不明", "AB", "AB"] }) exclude_list = ["height", "weight", "age"] list_cols = [x for x in df.columns if x not in exclude_list] cols = ["power"] # ce_ohe = ce.OneHotEncoder(cols=cols,handle_unknown='impute') # df_ce = ce_ohe.fit_transform(df) # print("■変更前") # print(df) # print("■変更後") # print(df_ce) # 文字列にNaNが含まれる場合は置換 def checkIncludeNaNstr(x): if type(x) == str and "nan" in x: x.replace("nan", "0") return x # 文字列がJSONかどうか def is_json_format(x): if type(x) != str: return x try: json.loads(x) result = convertStrToJson(x) return result except json.JSONDecodeError: return x # JSON文字列からJSONへ変換 def convertStrToJson(x): result = ast.literal_eval(x) return result # Seriesオブジェクトを作成するメソッド def create_series(dict_value): return pd.Series(list(dict_value.values()), index=list(dict_value.keys())) def checkJSON(x): if type(x) == json: return True else: return False df = df.applymap(lambda x: checkIncludeNaNstr(x)) df = df.applymap(lambda x: is_json_format(x)) df_convert_json_to_series = df["power"].apply(lambda x: create_series(x)) df = pd.concat([df, df_convert_json_to_series], axis=1) df.drop(columns='power', inplace=True) # df_list = [] # for column_name, item in df.iteritems(): # if (item.map(type) == json).count() > 0: # df_list.append(create_series(item)) # result_df = pd.concat([df, df_list], axis=1) # print(df_list) pd.set_option("display.unicode.east_asian_width", True) print(df)
ただ、入れ子になった「dict」とかには対応できてませんが、
⇧ 上記サイト様のコメント欄にあるように、「再帰」処理すれば対応できそうな感じですかね。
あと、そもそもとして、実際の業務では「カテゴリ変数」にすべき「列」の洗い出しをするところとかも実施すべきだとは思いますが、おそらく、以下のようなコードでいけるかと。
# 数値に変換できない値を持つ要素を持つ列(カラム)名のリストを作成するメソッド def getColumnByStrValueList(s, columList): try: pd.to_numeric(s, errors="raise") except Exception as e: columList.append(s.name) # 数値に変換できない値を要素として持つ列(カラム)名のリスト cloumnIncludStrValueLists = [] df.apply(lambda s: getColumnByStrValueList(s, cloumnIncludStrValueLists)) cloumnIncludStrValueLists
⇧ ってな感じで、数値以外のカラム名を特定できるかと。
それでは、以下を追記で「blood」の列を「カテゴリ変数」に変換で。
allowed_vals_list = ["A", "B", "AB", "O"] # 「血液型」以外の値は、"unknown"って文字列に置換 df.loc[~df["blood"].isin(allowed_vals_list), "blood"] = "unknown" # カテゴリ変数に変換 df_blood = pd.get_dummies(df['blood'], drop_first=True, dummy_na=False) df = pd.concat([df, df_blood], axis=1) df.drop(columns='blood', inplace=True)
⇧ 気になったのは、今回「A型」が元のデータに存在しないんだけど、「drop_first=True」を指定しちゃって良いのかがよく分からん...
⇧ そもそも、「weight」とか「age」で「不明」って値になってる部分ってのは、どういう値に置換すれば良いんですかね?
やっぱり、0とかにするのかな?
「平均値」とかが変な感じになりそうなんだけどな...
という感じで、「数値」に関するデータの「欠損値」の扱いが難しいんよね...
その答えはと言うと、
⇧ かの有名な「Kaggle」で紹介されておりました。
大まかに対処法を分類すると、
- Deletions
- Parwise Deletion
- Listwise Deletion/ Dropping rows
- Dropping complete columns
- Impulations
- General
- For Non Time Series
- Time Series
- Adavance
- General
⇧ 上記のような感じになり、「Deletions(削除する方針)」「Impulations(値を補完する方針)」のどっちかになるっぽい。
で、 「Impulations(値を補完する方針)」でいく場合は、「For Non Time Series(日付じゃないデータと思われる)」っぽいデータなら、
で値を補完すれば良いよ、ってことらしいんですが、ん~...そのどれを使ったら良いかが知りたいんだけどな...
まぁ、実際問題「40歳~3000歳」なんていうハチャメチャな年齢のレンジ(幅)は現実世界では起こり得ないんだけども、ゲームとか開発してて「機械学習」するケースとか出てきた場合、どうしてるんかね?
悩ましいところなんですが、今回は「mean(平均値)」で補完することに。
最終的には、以下のような感じで。(コードがむちゃくちゃ汚くなったけど...)
import pandas as pd import numpy as np import category_encoders as ce import json import ast import sys df = pd.DataFrame({ "iD": [1, 2, 3, 4, 5, 6, 7], "name": ["メリオダス", "バン", "ディアンヌ", "キング/ハーレクイン", "ゴウセル", "マーリン", "エスカノール"], "nickName": ["憤怒の罪(ドラゴン・シン)", "強欲の罪(フォックス・シン)", "嫉妬の罪(サーペント・シン)", "怠惰の罪(グリズリー・シン)", "色欲の罪(ゴート・シン)", "暴食の罪(ボア・シン)", "傲慢の罪(ライオン・シン)"], "species": ["魔神族", "元人間", "巨人族", "妖精族", "人形", "不明", "人間"], "power": ['{"magic": 400, "force": 990, "spirit": 2010 }', '{"magic": 1380, "force": 930, "spirit": 910 }', '{"magic": 900, "force": 1970, "spirit": 480 }', '{"magic": 3370, "force": 0, "spirit": 820 }', '{"magic": 1300, "force": 500, "spirit": 1300 }', '{"magic": 3540, "force": 70, "spirit": 1100 }', '{"magic": 5, "force": 5, "spirit": 5 }'], "height": ["152", "210", "915", "160", "173", "177", "165"], "weight": ["50", "70", "不明", "48", "61", "57", "49"], "age": ["3000", "43", "750", "1300", "不明", "不明", "40"], "blood": ["B", "B", "O", "AB", "不明", "AB", "AB"] }) exclude_list = ["height", "weight", "age"] list_cols = [x for x in df.columns if x not in exclude_list] cols = ["power"] # ce_ohe = ce.OneHotEncoder(cols=cols,handle_unknown='impute') # df_ce = ce_ohe.fit_transform(df) # print("■変更前") # print(df) # print("■変更後") # print(df_ce) # 文字列にNaNが含まれる場合は置換 def checkIncludeNaNstr(x): if type(x) == str and "nan" in x: x.replace("nan", "0") return x # 文字列がJSONかどうか def is_json_format(x): if type(x) != str: return x try: json.loads(x) result = convertStrToJson(x) return result except json.JSONDecodeError: return x # JSON文字列からJSONへ変換 def convertStrToJson(x): result = ast.literal_eval(x) return result # Seriesオブジェクトを作成するメソッド def create_series(dict_value): return pd.Series(list(dict_value.values()), index=list(dict_value.keys())) def checkJSON(x): if type(x) == json: return True else: return False df = df.applymap(lambda x: checkIncludeNaNstr(x)) df = df.applymap(lambda x: is_json_format(x)) df_convert_json_to_series = df["power"].apply(lambda x: create_series(x)) df = pd.concat([df, df_convert_json_to_series], axis=1) df.drop(columns='power', inplace=True) # df_list = [] # for column_name, item in df.iteritems(): # if (item.map(type) == json).count() > 0: # df_list.append(create_series(item)) # result_df = pd.concat([df, df_list], axis=1) # print(df_list) # 文字列のJSONが変換されたか確認 # print(df["power"].map(type)) allowed_vals_list = ["A", "B", "AB", "O"] df.loc[~df["blood"].isin(allowed_vals_list), "blood"] = "unknown" df_blood = pd.get_dummies(df['blood'], drop_first=True, dummy_na=False) df = pd.concat([df, df_blood], axis=1) df.drop(columns='blood', inplace=True) pd.set_option('display.max_rows', None) #pd.set_option("display.unicode.ambiguous_as_wide", True) #pd.set_option("display.html.table_schema", True) #print(df.to_csv(sys.stdout, sep=" ")) #print(df.to_string(justify="left", force_unicode=True)) pd.set_option("display.unicode.east_asian_width", True) print(df) # 数値に変換できない値を持つ要素を持つ列(カラム)名のリストを作成するメソッド def getColumnByStrValueList(s, columnList): try: pd.to_numeric(s, errors="raise") except Exception as e: columnList.append(s.name) # 数値に変換できない値を要素として持つ列(カラム)名のリスト cloumnIncludStrValueLists = [] df.apply(lambda s: getColumnByStrValueList(s, cloumnIncludStrValueLists)) print(cloumnIncludStrValueLists) # 数値と文字列が混在してる要素かそうでないかを判定 def checkAllCloumnsValueByStr(s, checkDict, columnList): columnList = [] for value in s: if type(value) == str: columnList.append(True) else: columnList.append(False) checkDict[s.name] = columnList # DataFrameの列の各要素の値のデータ型を確認する checkColumnsDict = {} checkClumnValueTypeList = [] df[cloumnIncludStrValueLists].apply(lambda s: checkAllCloumnsValueByStr(s, checkColumnsDict, checkClumnValueTypeList)) print(checkColumnsDict) # DataFrameの列で、要素の値のデータ型が全て同じもの(今回はstr型)のカラム名のリストを作成 onlyStrValueColumnList = [] for key in checkColumnsDict: if len(list(set(checkColumnsDict[key]))) == 1: onlyStrValueColumnList.append(key) print(onlyStrValueColumnList) # 数値と文字列が混在する列の値のうち、文字列はNaN値(欠損値)に置き換え def replaceStrToNan(s): for index in s.index: if type(s[index]) == str: s[index] = np.nan # SettingWithCopyWarning対策のため、一旦、新しい変数に退避 df_tmp = df[[i for i in list(df.columns) if i not in onlyStrValueColumnList]].copy() # 文字列はNaN値(欠損値)に置き換え df_tmp.apply(lambda s: replaceStrToNan(s)) print(df_tmp) # NaN値(欠損値)を列の平均値で置き換え df_tmp = df_tmp.fillna(df_tmp.mean().round().astype(int)) # 分割してたDataFrame同士を横(axis=1)で結合 df = pd.concat([df[onlyStrValueColumnList], df_tmp], axis=1) #df[[i for i in list(df.columns) if i not in onlyStrValueColumnList]].fillna(df.mean(), inplace=True) # 結局、マジックナンバー(ここでは、10)使っちゃったけど、「Tidy Data(整然データ)」に置き換え df = pd.melt(df, id_vars=df.columns.values[:10], var_name="blood", value_name="blood_value") print(df)
⇧ で実行してみると、
name nickName species iD height weight age magic force spirit blood blood_value 0 メリオダス 憤怒の罪(ドラゴン・シン) 魔神族 1 152 50 3000 400 990 2010 B 1 1 バン 強欲の罪(フォックス・シン) 元人間 2 210 70 43 1380 930 910 B 1 2 ディアンヌ 嫉妬の罪(サーペント・シン) 巨人族 3 915 56 750 900 1970 480 B 0 3 キング/ハーレクイン 怠惰の罪(グリズリー・シン) 妖精族 4 160 48 1300 3370 0 820 B 0 4 ゴウセル 色欲の罪(ゴート・シン) 人形 5 173 61 1027 1300 500 1300 B 0 5 マーリン 暴食の罪(ボア・シン) 不明 6 177 57 1027 3540 70 1100 B 0 6 エスカノール 傲慢の罪(ライオン・シン) 人間 7 165 49 40 5 5 5 B 0 7 メリオダス 憤怒の罪(ドラゴン・シン) 魔神族 1 152 50 3000 400 990 2010 O 0 8 バン 強欲の罪(フォックス・シン) 元人間 2 210 70 43 1380 930 910 O 0 9 ディアンヌ 嫉妬の罪(サーペント・シン) 巨人族 3 915 56 750 900 1970 480 O 1
⇧ 上記のような感じのデータになりましたと。
カラムの順番がめちゃくちゃになってるけども...
Tidy Data(整然データ)をどうやって機械学習に使えば?
で、試行錯誤の果てに「Tidy Data(整然データ)」が無事に作成できました、めでたしめでたし...で終わらないのが「データ分析」ですと。
というか、 「Tidy Data(整然データ)」をどうやって「機械学習」で使えるようにするのよ?って情報が見当たらんのですわ...
何を気にしてるかというと、「機械学習」で扱う「データ」って「数値」である必要があるはずじゃないですか?
「機械学習」で扱う「データ」って「数値」じゃなきゃダメよ、という前提がある以上、「Tidy Data(整然データ)」にすると確かに「データ」は見やすい形にはなるけれど、「数値」じゃない値のカラムができちゃったよね?
どうすれば良いの?
ここで衝撃の事実が...
※機械学習ではカテゴリ変数を扱うことが苦手なために、one-hot encodingという手法を用いてカテゴリ変数を0と1のダミー変数に変換することがあります。そのため上で示したような値が変数として用いられることがあります。つまり、データの「整った形」とは、どのような解析をするのかで変化することがあります。重要なことは、自分の解析に合わせて、データの構造を自由自在に変化させていくことです。
⇧ つまり、「Tidy Data(整然データ)」は「機械学習」で使えないデータになり得る可能性があるという...
ここで、「Tidy Data(整然データ)」について今一度確認。
整然データは、データセットを構造化する標準的手法を提供するため、分析者や計算機が必要な変数を抽出することを容易にする。
⇧ え、え~っと... ...
というわけで、残念ながら、「Tidy Data(整然データ)」にしてはいけない時もあるってことなんですかね?
「入力データ」については、「カテゴリ変数」へ変換後は「Tidy Data(整然データ)」に変換するのは諦めて、「機械学習モデル」に投入していくことになるのかな?
分からんことが多過ぎるな...
今回はこのへんで。