ねぎとろ放浪記

ねぎとろ放浪記

個人的備忘録です。勉強したことをまとめていきます。

【独学】基本情報技術者試験の勉強法

基本情報技術者試験に合格したので勉強法を記録しておきます。

2020年の秋試験がコロナで中止になり、CBT方式での受験になりました。

勉強期間:4+3ヶ月(コロナで延期)
勉強時間:1日15〜30分

午前試験

  • 教科書一周読んで練習問題を解く
  • 過去問サイトを1日5問
  • 試験前に過去問を4年分

教科書を1周してからは、ひたすら過去問サイトで勉強していました。

使い回しの問題もあるので、過去問サイトはかなりやりました。

過去問は時間の感覚を掴むのに1年分だけ解くだけでも十分だと思います。

午後試験

  • 過去問を4年分
  • データベース:PostgreSQL環境構築して基本構文の練習

SQLは午前試験にも役立つのでコスパ良いです。

環境構築が面倒な人はProgateなどのサイトで練習しておくだけでも有効だと思います。

プログラミング言語は普段から使っているものがあれば特に対策はいらないと思います。
Pythonは今年からの出題で過去問がありませんでしたが、なにも問題なかったです。

午後試験は読解量が多く、時間がシビアです。
過去問は数年分解いたほうが良いでしょう。

使った参考書


令和02年【春期】 基本情報技術者 パーフェクトラーニング過去問題集

令和02年【春期】 基本情報技術者 パーフェクトラーニング過去問題集

  • 作者:山本 三雄
  • 発売日: 2019/12/26
  • メディア: 単行本(ソフトカバー)

参考書はこの2冊だけでした。
ただしどちらも過去問サイトにあるので買わなくてもOKかと。

結果

CBT方式なので受験直後にスコアが確認できます。
午後試験のスコア概要だけなぜか葉書が送られてきます。  

合格発表は約1ヶ月後で、その後に合格証書が送られてきます。

得点:

  • 午前:87.5
  • 午後:90

試験日が延長されたこともあり、なかなか高得点。

最後に

以上、基本情報技術者試験の勉強法でした。

この記事が誰かの参考になれば幸いです。

PostgreSQL操作まとめ

よく使う操作の自分用備忘録です。

目次

データベース一覧を表示

psql -l

データベースに接続

psql データベース名

テーブル一覧

\dt

postgreSQLを終了

\q



データベースの作成

createdb データベース名

テーブルの作成

create table テーブル名 (
    id INTEGER NOT NULL,
    name varchar NOT NULL,
    age INTEGER,
    gender INTEGER
    PRIMARY KEY (id)
);

id,name,age,genderの4つのカラムのテーブルを作成しました。

データの更新

update テーブル名 set 列名=データ, 列名=データ,....;

where条件も指定できる。

カラムの追加

alter table テーブル名 add 列名 型名;

カラムの削除

alter table テーブル名 drop column カラム名;

カラム名の変更

alter table テーブル名 rename 元のカラム名 to 新しいカラム名;

テーブルの削除

drop table テーブル名;



レコード追加

insert into テーブル名 (カラム名, カラム名) values(値,値);

レコード削除

delete from テーブル名 where 条件;

CSVからインポート

\copy テーブル名 from CSVのパス with csv


とりあえずこれだけあればなんとかなるはず

causallibでIPWをやってみる

因果推論の勉強をはじめました。
IPW法を学んだのでPythonで実際にやってみます。

causallibというライブラリを使ってIPW法をやってみました。

こちらのサンプルに沿って進めていきます。

実行環境

  • Googl Colaboratory
  • causallib (0.6.0)
  • sklearn (0.22.2)

使用するデータの確認

喫煙をやめたことによる体重の変化を検証します。

import causallib
import sklearn

data = load_nhefs()
data.X.join(data.a).join(data.y).head()

f:id:neginegitoro:20201226183344p:plain
データの一部

データの内容はざっくりと以下のようになっています。

  • data.X : 共変量 (年齢、性別、喫煙年数、運動習慣など)
  • data.a : 割当(喫煙をやめていれば1、やめていなければ0)
  • data.y : 目的変数 (体重の増減(ポンド))


割当を確認します。

#割当
data.a.value_counts()
0    1163
1     403
Name: qsmk, dtype: int64

1163人は喫煙のまま、403人が禁煙したようです。

目的変数を確認します。

#目的変数
data.y.describe()
count    1566.000000
mean        2.638300
std         7.879913
min       -41.280470
25%        -1.478399
50%         2.603811
75%         6.689581
max        48.538386
Name: wt82_71, dtype: float64

体重は平均で2.6ポンド増加したようです。


禁煙した人としなかった人での体重の変化を比較してみます。

df = data.X.join(data.a).join(data.y)

print("割当1 : ", df[df.qsmk == 1]["wt82_71"].mean())
print("割当0 : ", df[df.qsmk == 0]["wt82_71"].mean())
割当1 :  4.525078989702233
割当0 :  1.9844975347463472

禁煙した人の方が体重が増加していることがわかります。

IPW

傾向スコアの予測

サンプルの通りに実装してみます。

#train
learner = LogisticRegression(solver="liblinear") #傾向スコア予測に使うモデル
ipw = IPW(learner)
ipw.fit(data.X, data.a) #共変量から割当を予測

#傾向スコア
probs = ipw.compute_propensity(data.X, data.a, treatment_values=1)#割当1になる確率

probs.head()
0    0.131687
1    0.168435
2    0.169179
3    0.481203
4    0.251937
Name: 1, dtype: float64

ここの値は割当1になる確率です。

重み付けの確認

傾向スコアの逆数で重み付けされていることを確認します。

ipw.compute_weights(data.X, data.a).head()
0    1.151658
1    1.202552
2    1.203629
3    1.927535
4    1.336785
dtype: float64
  • 割当1のデータは 1/傾向スコア
  • 割当0のデータは 1/(1 - 傾向スコア)

の値になっています。

結果を確認

#割当ごとに重みづけして結果を計算
outcomes = ipw.estimate_population_outcome(data.X, data.a, data.y)

#ATE
effect = ipw.estimate_effect(outcomes[1], outcomes[0])
effect
diff    3.490373
dtype: float64

禁煙によって3.4ポンド体重が増加したようです。

パラメータを変化させる

次にパラメータを変化させて実行してみます。

傾向スコアが0に近すぎると、逆数をとった時に極端に大きい値になります。

また、1に近すぎると、影響が小さくなってしまいます。

今回は傾向スコアを0.2から0.8の範囲に限定します。

learner = LogisticRegression(penalty='l1', C = 0.01, max_iter=500, solver = 'liblinear')

#傾向スコアを0.2から0.8に限定
truncate_eps = 0.2
ipw = IPW(learner, truncate_eps = truncate_eps, use_stabilized=False)
ipw.fit(data.X, data.a) #共変量から割当を予測

傾向スコアの確認

#傾向スコア
probs = ipw.compute_propensity(data.X, data.a, treatment_values=1)#割当1になる確率

print(probs.min())
print(probs.max())
Fraction of values being truncated: 0.23499.
0.7047037999493072
0.2

ちゃんと0.2から0.8の間に収まっています。

結果の確認

#割当ごとに結果を重み付け
outcomes = ipw.estimate_population_outcome(data.X, data.a, data.y)

#ATE
effects = ipw.estimate_effect(outcomes[1], outcomes[0], effect_types=["diff", "ratio"])

effects
Fraction of values being truncated: 0.23499.
diff     3.376068
ratio    2.917687
dtype: float64

禁煙による体重の変化は3.37ポンドとなりました。

モデルの評価を行う

最後にモデルの評価をします。

evaluator = PropensityEvaluator(ipw)
results = evaluator.evaluate_simple(data.X, data.a, data.y, plots=None)

results.scores.prediction_scores

f:id:neginegitoro:20201226183419p:plain

見慣れない指標について

  • hinge : ヒンジ損失。(参考)
  • matthews : マシューズの相関係数。-1から1。1に近いほど良い分類ができている。(参考
  • 0/1 : ???知ってる人教えてください
  • brier : ブライアスコア。0から1で、0に近いほど正確であることを表す。(参考)


trainとvalidで表示されます。

from sklearn import metrics

plots = ["roc_curve", "pr_curve", "weight_distribution", "calibration", "covariate_balance_love", "covariate_blance_slope"]
metrics = {"roc_auc": metrics.roc_auc_score,
           "avg_precision": metrics.average_precision_score,}

ipw = IPW(LogisticRegression(solver="liblinear"))
evaluator = PropensityEvaluator(ipw)
results = evaluator.evaluate_cv(data.X, data.a, data.y, 
                                plots=plots, metrics_to_evaluate=metrics)

f:id:neginegitoro:20201226183636p:plain

  • 左上:ROC曲線。AUCは0.7以上だと良いとされています。
  • 右上:PrecisionとRecallのグラフ
  • 左中:傾向スコアの分布。割当0は0に近いほど多く、割当1は1に近いほど多いと良いようです。
  • 右中:キャリブレーションカーブ。横軸は傾向スコア。縦軸は割当1のデータの割合。x=yの直線に近いほど良い。
  • 左下:共変量の平均差を標準誤差で割ったものの絶対値??(ここがよくわかりませんでした)
  • 右下:左下と同じ。


独自データに適用してみる

予め因果効果のわかっているデータを作成し、精度を検証したいと思います。

データの作成

年齢と性別によって目的変数yが変化するデータを作成します。
割当zは 年齢と性別からシグモイド関数によって決定しました。

from numpy.random import *
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

#データ数
num_data = 10000

#性別
gender = randint(0, 2, num_data)

#年齢
age = randint(20, 81, num_data)

#ノイズの生成,randnは標準正規分布
e_z = randn(num_data)

#シグモイド関数に入れる部分
#これで割当を決める
#ここを調節することで傾向スコアが満遍なく散らばるようにする
z_base = 5*gender + age - 50 + 5*e_z

#シグモイド関数
def sigmoid(x):
  return 1/(1+np.exp(-x))

#シグモイド関数を計算
z_prob = sigmoid(0.1*z_base)

#割当の配列を作成
z = np.array([])
for i in range(num_data):
  tmp = np.random.choice([0,1], 
                         size=1, 
                         p=[1-z_prob[i], z_prob[i]])[0]#pは選ぶ確率
  z = np.append(z, tmp)#配列の更新

#目的変数のノイズを生成
e_y = randn(num_data)

#目的変数を決める
#ここのzの係数が因果効果
y = 5000 + 10*gender + 30*age + 60*z + 10*e_y

割当zによる目的変数の変化は60にしました。

データフレームの作成

causallibで使えるようにデータを整形します。

#データフレームの作成
df = pd.DataFrame({'gender':gender, 'age': age, 'z':z, 'y':y})

df_x = df[['gender', 'age']]
df_y = df['y']
df_z = df['z']

df.head()
gender age z y
0 0 76 1 7350.62
1 1 60 0 6784.8
2 1 34 0 6036.7
3 1 67 1 7089.36
4 0 50 0 6514.17

このようなデータを作成しました。

IPW法を適用する

これまで通りIPW法を適用します。

truncate_epsは0.01にしました。

#train
learner = LogisticRegression(solver="liblinear")
ipw = IPW(learner, truncate_eps=0.01)
ipw.fit(df_x, df_z)

#効果の確認
outcomes = ipw.estimate_population_outcome(df_x, df_z, df_y)

#(割当1の目的変数 * 1/傾向スコア) - (割当0の目的変数 * 1/(1-傾向スコア))の合計
effect = ipw.estimate_effect(outcomes[1], 
                            outcomes[0], 
                            effect_types=["diff", "ratio"])
effect

出力

Fraction of values being truncated: 0.00000.
diff     65.454931
ratio     1.010065
dtype: float64

割当の有無によるyの差は65.5と予測されました。
zの係数は60にしていたので、近い値ではありますね。

なお、同じ方法でデータを作成しても、予測値はばらつきました。 (55~80くらい)
何回か実行して平均をとった方が良いのかもしれないです。

評価

最後に評価を行います。

#評価
from sklearn import metrics

plots=["roc_curve", "pr_curve", "weight_distribution", 
       "calibration", "covariate_balance_love", "covariate_balance_slope"]

metrics = {"roc_auc": metrics.roc_auc_score,
           "avg_precision": metrics.average_precision_score,}

ipw = IPW(LogisticRegression(solver="liblinear"),
          truncate_eps=0.01)

evaluator = PropensityEvaluator(ipw)

results = evaluator.evaluate_cv(df_x, df_z, df_y, 
                                plots=plots, metrics_to_evaluate=metrics)

f:id:neginegitoro:20210103153906p:plain

AUCが0.85と高く、 傾向スコアは理想的な形に分布しています。

データとしてはかなり予測しやすいものだったのではないでしょうか。

最後に

以上causallibによるIPW法でした!

因果推論初心者ですが、公式のサンプルがわかりやすかったのでなんとか理解できたかと。。。

評価方法やその基準についてわかっていないことが多いので、今後勉強していこうと思います。

SQLで日付の操作

日付とか年齢とかの計算方法を忘れがちなので備忘録。

よく使う日付に関する型は以下の2つです。

  • date型 : 日付まで。'YYYY-MM-DD'
  • timestamp型 : 日付と時間。'YYYY-MM-DD HH:MM:SS'

今回は以下の表を操作します。

date_test表

 int_date |   str_date   |       ts_date       
----------+--------------+---------------------
 20200101 | 2020年1月1日 | 2020-01-01 00:00:00
 20200201 | 2020年2月1日 | 2020-02-01 00:00:00
 20200301 | 2020年3月1日 | 2020-03-01 00:00:00


文字列からdate型への変更

TO_DATE()を使います。

SELECT TO_DATE(str_date, 'YYYY年mm月dd日')
FROM date_test;

実行結果

   to_date   
------------
 2020-01-01
 2020-02-01
 2020-03-01
(3 rows)


intからdate型に変更

文字列に変換してからdateに変換します。

SELECT TO_DATE(TO_CHAR(int_date, 'FM99999999'), 'YYYYmmdd')
FROM date_test;

実行結果

  to_date   
------------
 2020-01-01
 2020-02-01
 2020-03-01
(3 rows)


timestampからdate型に変更

CAST(カラム名 AS DATE)を使います。

SELECT CAST(ts_date AS DATE) 
FROM date_test;

実行結果

 ts_date  
------------
 2020-01-01
 2020-02-01
 2020-03-01
(3 rows)


日付に関する演算

期間内のレコードを取得

date型同士は普通に足し引きできる。

SELECT str_date 
FROM date_test
WHERE '2020-04-01' - TO_DATE(str_date, 'YYYY年mm月dd日') < 40; 

実行結果

    date    
------------
 2020-03-01
(1 row)

2020年4月1日から40日以内のレコードのみ取得しました。


年齢のカラムを作成してみる

2038年時点での年齢を計算しました。

SELECT ('2038-01-01' - CAST(ts_date AS DATE) )/365 as age 
FROM date_test;

実行結果

 age 
-----
  18
  17
  17

pandasでの型変換

pandasでの型変更をよく忘れるのでメモしておきます。

datetime型に変更するのか、それ以外かで処理が変わります。

目次:

datetime以外に変更

astype()を使います。

#dfの作成
data = np.array([[1,2,3],[4,5,6],[7,8,9]])
index = ['one', 'two', 'three']
columns = ['a','b','c']

df = pd.DataFrame(data, index=index, columns=columns)

型を変更する

#strに変更
new_df = df.astype('str')
new_df.dtypes

実行結果

a    object
b    object
c    object
dtype: object

intからstrに変更しました。

pandasではstrはobject型と表記されます。

datetimeに変更する

datetime型に変換する時は、to_datetime()を使います。

YYYYmmdd YYYY/mm/dd
0 20200101 2020/01/01
1 20200201 2020/02/01
2 20200301 2020/03/01
#datetimeに変換する
df['YYYYmmdd'] = pd.to_datetime(df['YYYYmmdd'])
df['YYYY/mm/dd'] = pd.to_datetime(df['YYYY/mm/dd'])
df

実行結果

YYYYmmdd YYYY/mm/dd
0 2020-01-01 2020-01-01
1 2020-02-01 2020-02-01
2 2020-03-01 2020-03-01

基本的なフォーマットなら勝手に変更してくれます。

年月日表記int から datetime にしたい時は、フォーマットを指定します。

年月日 int
0 2020年1月1日 20200101
1 2020年2月1日 20200201
2 2020年3月1日 20200301

このような表記の時は、フォーマットを指定して変換します。

#フォーマットを指定して変換
df['年月日'] = pd.to_datetime(df['年月日'], format='%Y年%m月%d日')
df['int'] = pd.to_datetime(df['int'], format='%Y%m%d')
df

実行結果

年月日 int
0 2020-01-01 2020-01-01
1 2020-02-01 2020-02-01
2 2020-03-01 2020-03-01

datetime型に変換できました。

フォーマットの指定方法は下記の通りです。

  • %Y : 年(4桁)
  • %y : 年(2桁)
  • %m : 月
  • %d : 日
  • %H : 時
  • %M : 分
  • %S : 秒




以上pandasでの型変換でした。

他にも忘れがちな操作があれば書き足します。

pandasで条件指定して抽出、分岐

pandasの条件指定で表を操作します。

条件を指定する方法を忘れがちなのでメモっておきます。

目次:

今回使用するdf

data = np.array([[1,2,3],[4,5,6],[7,8,9]])
index = ['one', 'two', 'three']
columns = ['a','b','c']

df = pd.DataFrame(data, index=index, columns=columns)
df
a b c
one 1 2 3
two 4 5 6
three 7 8 9

このdfをいじっていきます。

条件を指定して抽出

1つの列について条件を指定して抽出する時

#'a'のカラムが奇数なら抽出
df[df['a']%2 == 1]
a b c
one 1 2 3
three 7 8 9

a列が奇数なら抽出。

2つ以上の条件を指定するときは&でつなぐ。

#'a'が奇数、'c'が9以上
df[(df['a']%2 == 1) & (df['c'] >= 9)]
a b c
three 7 8 9

&の他に|(or)も使えます。

条件で処理を分岐させるとき

applyを使います。

#関数を定義する
def func(x):
  if x > 5:
    return 1
  else:
    return 0

#c列にapplyする
df['d'] = df['c'].apply(func)
df
a b c d
one 1 2 3 0
two 4 5 6 1
three 7 8 9 1

定義した関数をc列に適用します。

ラムダ式を使うと簡潔に書けます。

#c列が5以上なら1
df['frag'] = df['c'].apply(lambda x : 1 if x > 5 else 0)
a b c flag
one 1 2 3 0
two 4 5 6 1
three 7 8 9 1


複数の列を条件に分岐させることもできます。

#関数定義
#2つのカラムを条件にしたいとき
def func(x):
  if x.a * x.b < 30:
    return 0
  else:
    return 1

#dfに対してapplyする
df['frag2'] = df.apply(func, axis=1)
a b c flag flag2
one 1 2 3 0 0
two 4 5 6 1 0
three 7 8 9 1 1

複数の列を条件に分岐させる時はdf自体にapplyします。



以上pandasでの条件指定方法でした。

これ以外にも忘れがちな条件指定があれば書き足していきます。

causallibでダブルロバストをやってみる

因果推論の勉強をはじめました。
ダブルロバスト法を学んだのでPythonで実際にやってみます。

今回はcausallibというライブラリを用いて、公式のサンプルに沿って進めていきます。

実行環境

  • Googl Colaboratory
  • causallib (0.6.0)
  • sklearn (0.22.2)

データの確認

今回は禁煙による体重の変化を推論します。

from sklearn.linear_model import LogisticRegression, LinearRegression
from causallib.datasets import load_nhefs
from causallib.estimation import IPW, Standardization, StratifiedStandardization
from causallib.estimation import DoublyRobustVanilla, DoublyRobustIpFeature, DoublyRobustJoffe
from causallib.evaluation import PropensityEvaluator, OutcomeEvaluator

data = load_nhefs()
data.X.join(data.a).join(data.y).head()

f:id:neginegitoro:20201224213129p:plain
データの一部

データの内容はざっくりと以下のようになっています。

  • data.X : 共変量 (年齢、性別、喫煙年数、運動習慣など)
  • data.a : 割当(喫煙をやめていれば1、やめていなければ0)
  • data.y : 目的変数 (体重の増減(ポンド))

    割当の内訳を確認します。
#割当
data.a.value_counts()
0    1163
1     403
Name: qsmk, dtype: int64

1163人は喫煙のまま、403人が禁煙したようです。

次に、目的変数(体重の変化)について確認します。

#目的変数
data.y.describe()
count    1566.000000
mean        2.638300
std         7.879913
min       -41.280470
25%        -1.478399
50%         2.603811
75%         6.689581
max        48.538386
Name: wt82_71, dtype: float64

体重は平均で2.6ポンド増加したようです。

禁煙した人としなかった人での体重の変化を比較してみます。

df = data.X.join(data.a).join(data.y)

print("割当1 : ", df[df.qsmk == 1]["wt82_71"].mean())
print("割当0 : ", df[df.qsmk == 0]["wt82_71"].mean())
割当1 :  4.525078989702233
割当0 :  1.9844975347463472

単純な比較では、禁煙した人は、しなかった人よりも2.54ポンド増加していることがわかります。

ダブルロバスト

causallibには3種類のダブルロバストがありました。
よくわからなかったのですが、公式ドキュメントを読んだ感じ以下の違いがあるようです。

  • Doubly Robust Vanilla : 通常のダブルロバスト
  • Doubly Robust IP-Feature : 反実仮想を求める際に、特徴量にIPWも加える。
  • Doubly Robust Joffe : 反実仮想を求める際に、IPWで重み付けしている。

今回はDoubly Robust Vanillaを使用しました。

ipw = IPW(LogisticRegression(solver="liblinear"), truncate_eps = 0.05)#ロジスティック回帰でIPW

std = StratifiedStandardization(LinearRegression())#反実仮想を求めるモデル

dr = DoublyRobustVanilla(std, ipw)#反実仮想を求めるモデル,傾向スコアを求めるモデル

dr.fit(data.X, data.a, data.y)#共変量のDF、割当のSeries、目的変数のSeries


個々人の結果を確認してみます。

#ITEの確認
ind_outcome = dr.estimate_individual_outcome(data.X, data.a)
ind_outcome.head()
qsmk   0  1
0  4.174723   7.564068
1  5.715355   10.489504
2  1.069715   8.723563
3  -3.004029  -2.249906
4  1.303085   8.072923


全体の結果を確認します。

pop_outcome = dr.estimate_population_outcome(data.X, data.a)

effect = dr.estimate_effect(pop_outcome[1], pop_outcome[0])

effect
diff    3.443486
dtype: float64

禁煙によって3.4ポンド体重が増加という結果になりました。

評価

次に、モデルの評価を行います。
まず傾向スコアを求めるモデルから評価します。

#傾向スコアのモデルについての評価
prp_evaluator = PropensityEvaluator(dr.weight_model)
results = prp_evaluator.evaluate_simple(data.X, data.a, data.y,
                                        plots=["roc_curve", "weight_distribution"])
results.scores.prediction_scores

f:id:neginegitoro:20201224211432p:plain

上のグラフはROC曲線です。一般に0.7以上だと良いとされているようです。

下のグラフは傾向スコアの分布です。
理想的な分布は

  • treatment=0:0に近いほど多い
  • treatment=1:1に近いほど多い

とされていますが、それとは大きく異なっています。
傾向スコアを求めるモデルを変更するともっといい結果が出るかもしれません。


次に反実仮想を求めるモデルの評価です。

#反実仮想を求めるモデルの評価
out_evaluator = OutcomeEvaluator(dr)
results = out_evaluator.evaluate_simple(data.X, data.a, data.y, 
                                        plots=["common_support", "continuous_accuracy"])
results.scores

f:id:neginegitoro:20201224211530p:plain

下のグラフが実測値と予測値の分布です。

最後に

非常に簡単にダブルロバストを行うことができました!

今回はサンプルを使ってみただけで、結果をどの程度信頼していいのかよくわかっていません。。。

今後、その他の手法とモデルの評価方法を勉強していこうと思います。

以上causallibによるダブルロバストでした。