備忘録とか日常とか

学んだこととかを書きます。

中古車購入に向けてのデータ分析をしてみたかった【python, scraping】

中古車を対象としたなんちゃってデータ分析をしてみた。以下の本を読んでなんか作ってみたくなったので。n番煎じなのは気にしない。

本のレビューもそのうち書くかもしれない。

動機

若者の車離れが叫ばれて(?)久しいが、自分としては昔からマイカーは憧れの象徴なので車が欲しい。 当然お金はないので中古車を買うことにはなるが、中古車は様々な要素から価格が決まってくる。

  • 年式、走行距離

  • 修復歴あり or なし

  • カーナビなどオプションの有無

値段に強く影響しそうなのはこの辺な気がする。車の素人が購入を判断するための材料が欲しいので、色々データをいじってみる次第である。 まずはwebサイトから各種情報をとってくるところから始める。

スクレイピング

ざっくり流れ

requestsでHTTP接続し、 lxml.htmlでパースする。今回はログインなどはせず、ステートレスなクロールを行うので特に複雑な処理は発生しない。取得したデータはpymongoでMongoDBに保存する。 MongoDB初めて使ったけど便利だなこれ。

カーセンサーのページを対象とする。カーセンサーは特定の条件で検索すると、その条件に合致した中古車の一覧ページが生成され、その一覧から気になった車をクリックすると その車の詳細ページを閲覧できる。なので「一覧ページを取得 -> 詳細ページのhtml取得 -> スクレイピング」という手順となる。

ソースコード

コードは以下。

github.com

動作環境は

  • MongoDB v4.0.1
  • pymongo 3.7.1
  • scikit-learn 0.20.0
  • statsmodel 0.9.0

ぐらいあれば動くと思う。

car_scrape.pyスクレイピング実行してMongoDBにデータ保存するスクリプト。実行時にコマンドライン引数で車種を指定する。とりあえずPRIUS, VEZEL, FITに対応しているが、URLを追加すれば他の車種も取得可能。 車一台につき固有のIDを割り当てて車両情報と一緒にDBに保存し、次にスクリプトを実行するときはIDが未登録の車両のみをDBに追加することができる。新たに追加された中古車を登録するのに便利な設計(ただし 車両価格更新には対応できない...)。オプション引数に--reset_dbを指定すると、MongoDBのcollectionを削除して一からスクレイピングを行う。

中身はえらいごちゃごちゃしてしまったが、CSS selectorとスクレイプのとこが冗長になってしまっただけなので、処理自体は単純。scrape_list_page()で一覧ページを読み込み、その中でscrape_detail_page()を順次呼び出して 詳細ページのhtmlを取得する。また今回は車種だけ指定し、その他の条件(グレード等)は特に指定せずにスクレイピングする形をとった。

visualize.ipynbでは取得したデータの可視化を行い、analyze.ipynbで簡単な分析ごっこをしてみた。後述。

可視化してみる

カーセンサーに掲載されている中で数が最も多い普通車がプリウスっぽかったので、プリウスについて調べた。

年式 - 本体価格

まずは年式と本体価格の関係を散布図で見てみる。 f:id:may46onez:20181106080957p:plain

横軸のmodel_yearは年式、縦軸のbase_priceは車両本体価格(単位:万円)となっている。赤い線とプロットは各年代の中央値を示している。まあ当然っちゃ当然だが、年式が新しいほうが価格が高い。そしてパッと見、分布の重心は価格の低い方に位置しており、高価な車両はまばらに分布していることがわかる。これはカスタムすればするだけ車は高くなるので、正規分布より若干ロングテール気味になるからであると思われる。

上の散布図を見ると、2015年から2016年で価格が大きく上がっているように見える。確認のために本体価格のヒストグラムをプロットしてみる。 f:id:may46onez:20181107080511p:plain

これを見ると明らかに山が二つあり、年式の違いで価格に大きな隔たりがあることが確認できる。もうちょい詳しく見たいのでプロットする年式を絞ってみる。 f:id:may46onez:20181107081302p:plain

やはり2015年と2016年の分布に隔たりがある。中央値だと50~60万円ほど変わってくるか。実はプリウスは2015年12月にフルモデルチェンジされており、その影響で旧モデルとの価格差が顕著になっている。 ちなみにその前のフルモデルチェンジは2009年5月であり、2008 ~ 2009年でも価格が見受けられる(ただし価格差は直近のモデルチェンジよりは控えめ)。要は旧モデルは露骨に安くなることがわかったので、特にこだわりがなければモデルチェンジ後の車両なら比較的安く手に入れられる。逆にモデルチェンジが発表された車種はちょっと購入を遅らせてみてもよいのかもしれない。

走行距離 - 本体価格

次に走行距離を見てみる。ネットで中古車購入の手引き的なページを見ると、

走行距離〇万km(3万, 5万とか)を超えると値段がかくっと下がる

といった記述が散見される。4.9万kmと5.1万kmでは心理的なハードルが違ってくるかららしい。まあ何となくそんな気もしなくはない。

確かめるために、横軸に走行距離distance(単位:万km)、縦軸に本体価格base_priceをとり、年式ごとにプロットする。

f:id:may46onez:20181111214842p:plain

色が赤に近づくほど年式が新しくなる。年式は新しいほど価格が上がり、走行距離が伸びると価格が下がるといったことが読み取れる。まあ当たり前だけど。

色が重なりすぎて見づらいので2015年以降でプロット f:id:may46onez:20181111215728p:plain

最新の年式は試走等の目的で使われた後中古に流れたいわゆる新古車が多く、ほぼ0kmのものが多い。カスタム内容を選ばなければ意外と安く最新モデルが手に入りそうだ。 そして前述の通り16年と15年には価格の隔たりがある。16年は6万kmを超えたあたりから価格の下落が大きくなっているような気がする。

ついでに2011年 ~ 2015年もプロットする。

f:id:may46onez:20181111221915p:plain

この辺の年式は結構値段が似通ってるので、分布ががっつり重なってくる。11年は若干安いくらいか。こうしてみると、どうせ値段同じくらいなんだし14年の車買ったほうがお得なんじゃないかと思えてくる (他の条件を無視しているので実際はわからん)。 そして肝心の、「〇万km超えると安くなる説」はこの図からはほぼ読み取れない。実際全く同じ色、グレード、カスタムの車種を比べると気持ち〇万km超えのほうが安くなるのかもしれないが、 中古市場において全く同条件の車両が出回ることはほぼあり得ないので、走行距離の〇万kmはほぼ意識しなくてよいと結論付けることにした。そこにこだわるくらいなら別のとここだわったほうが良い。

分析あれこれ

なんかデータ分析っぽいことがしてみたかったので、やってみた。

特徴選択

車両の価格に影響しそうな情報として、オプションがどの程度ついているかという情報を取得した。

f:id:may46onez:20181127224312p:plain
サンプルのオプション例

走行距離、年式に加えて上記のオプション情報を使用して、車両価格を推定する。これらは全て二値のダミー変数として扱う。 カーナビは種類が複数あり(CD, DVD, HDD, メモリナビ)、面倒なのでHDD、メモリナビは1, それ以外は0とする。同じ理由でヘッドランプもディスチャージドランプとLEDランプはまとめて1とする。オーディオはカーナビと 一体となっている車が多いので使用せず、他にも3列シートとかウォークスルーとか車種に依存しそうな情報は抜きにした。あとは禁煙車などを加味して、結果的に全部で22項目を使用することになった。 走行距離、年式は平均0, 分散1になるように正規化する。

まずはどの特徴が価格に大きく寄与しているのかを調べる。 特徴選択の方法は種々あって、重回帰分析でよく利用されるのは変数増減法とからしい。scikit-learnにもいくつか用意されている(指定した評価指標で上位k個を選択するSelectKBest, 変数減少法のRFE, モデルから算出される 重要度や係数を使用するSelectFromModel)。

今回はscikit-learnのRandomForestRegressorを使用し、feature_importance_を 算出して重要度が大きい順に特徴を選択してみる(それぞれの特徴が相対的に見てどれくらい重要度が高いかをみたかったので、ここではSelectFromModelは使用しなかった)。 RandomForestはブートストラップサンプルに対して決定木を学習させるアンサンブル手法で、out-of-bag誤り率を算出することで特徴の重要度を図ることができる 手法である。このサイトとかはじパタとか読めばわかりやすいと思う。

以下analyze.ipynbのコードの一部を抜粋
念の為データをtest と trainに分けて、trainデータのみで特徴選択をしている。

rf = RandomForestRegressor(n_estimators=200, random_state=50)
rf.fit(x, y)

fi = rf.feature_importances_
result = []
for i, label in zip(fi, x_data_df.columns):
    result.append((i, label))
    
result.sort(reverse=True)
v = []
for i, label in result:
    print("{0:15}  {1:0.6}".format(label, i))
    v.append(label)

出力は以下

model_year       0.836925
distance         0.0789052
lowdown          0.0168161
aero             0.00976041
repare           0.00806706
cruise           0.00589656
auto_brake       0.00579045
record_book      0.0051595
sheat_heater     0.00469167
camera           0.00400534
ETC              0.00370045
anti_theft       0.00357377
navi             0.00314469
AS_sensor        0.00273345
ESC              0.00260764
parking_assist   0.00197162
cold_area        0.00177578
smartkey         0.00158297
alumi_wheel      0.00135012
keyless          0.000664804
ABS              0.000528251
sheat_air        0.000165102
around_camera    0.000112198
liftup           7.20092e-05

重要度降順結果。年式、走行距離に続いてローダウン、フルエアロ、修復歴、クルーズコントロール、衝突被害軽減ブレーキ・・・と続いていくことがわかる。 ローダウンとフルエアロあたりが装備されている車は前オーナーが車好きである可能性が高く、そういう車はほかにもいっぱいオプションがついてたりするので価格が高くなる傾向にあるのかもしれない(未確認)。 その次が修復歴なので、やっぱり事故車は価格にかなり影響するのだなあと実感。

次に、適当にモデルを構築してみる。重要度上位10個の特徴を使用した場合と全て使用した場合について、三つのモデル(線形回帰、RandomForest, SVR)でcross validationしてみた。

以下コード抜粋

# cross validationで各モデルを学習・評価
# 評価指標には adjusted r2
# linear regression, random forest, svrを使用

x2 = x_data_df.loc[:, v[:10]].values
linear_reg = linear_model.LinearRegression()
rf = RandomForestRegressor(n_estimators=200, random_state=50)
svr = SVR(gamma='scale')
num_y = y.shape[0] // 10

lr_scores_sub = cross_val_score(linear_reg, x2, y, cv=10, scoring='r2')
rf_scores_sub = cross_val_score(rf, x2, y, cv=10, scoring='r2')
svr_scores_sub = cross_val_score(svr, x2, y, cv=10, scoring='r2')
lr_scores_all = cross_val_score(linear_reg, x, y, cv=10, scoring='r2')
rf_scores_all = cross_val_score(rf, x, y, cv=10, scoring='r2')
svr_scores_all = cross_val_score(svr, x, y, cv=10, scoring='r2')

lr_adr2_sub = adjusted_r2(lr_scores_sub, num_y, x2.shape[1])
rf_adr2_sub = adjusted_r2(rf_scores_sub, num_y, x2.shape[1])
svr_adr2_sub = adjusted_r2(svr_scores_sub, num_y, x2.shape[1])
lr_adr2_all = adjusted_r2(lr_scores_all, num_y, x.shape[1])
rf_adr2_all = adjusted_r2(rf_scores_all, num_y, x.shape[1])
svr_adr2_all = adjusted_r2(svr_scores_all, num_y, x.shape[1])

for a in [lr_adr2_sub, rf_adr2_sub, svr_adr2_sub, lr_adr2_all, rf_adr2_all, svr_adr2_all]:
    print('mean:{0:.5}    std:{1:.5}'.format(np.mean(a), np.std(a)))

相変わらず見づらいクソコードである。結果は以下

mean:0.87579    std:0.0078976
mean:0.90187    std:0.0049071
mean:0.89518    std:0.0054207
mean:0.87769    std:0.0073689
mean:0.90777    std:0.0063641
mean:0.89033    std:0.0057512

10 foldのcross validationで、平均と標準偏差を算出した。上三つが特徴量10個、下三つが全特徴使用。上から順に線形回帰、RandomForest、SVR、・・・の結果となる。

ここでは特徴の数が多いほうが精度が少し上がっているように見える。線形回帰においては多重共線性のある特徴が含まれていると精度が下がるはずなので、

  1. 評価指標に問題があった
  2. 特徴同士の相関が低かった

が原因かと思った。変数が増えるとモデルの近似がうまくいってなくてもR2は勝手に増加するそうで、それを調整したのがadjusted R2なのだが、なにか使い方に問題があったのかも知れない… 。 まあお遊びなのでそこまで厳密にする必要はないのだけれども。

特徴同士の相関が低い、とは思えない。なぜならスマートキーがついてるのにキーレスじゃないとかありえないし、衝突被害軽減ブレーキがついてたら障害物センサーもついてるんじゃないの?と思うから。 というか大半がダミー変数の場合の回帰ってうまくいくのだろうか。本職の人はどう判断するのか気になります。

あと特徴選択するなら各fold毎に重要度を求めてやらないと意味ないのだが、ここはめんどくさくてさぼってしまった。これをちゃんとしたら案外うまくいくのかもしれない。

statsmodelによる分析

statsmodelなるライブラリを使うと回帰分析のsummaryがすぐ見れちゃうとのことなので使ってみた。 先ほど選択した特徴量を入力として全データに対して回帰分析してみる。

以下コード抜粋

x_sm_sub = sm.add_constant(x_data_df.loc[:, v[:10]])
model = sm.OLS(y, x_sm_sub) 
results = model.fit()
print(results.summary())

結果

                            OLS Regression Results                            
==============================================================================
Dep. Variable:                      y   R-squared:                       0.878
Model:                            OLS   Adj. R-squared:                  0.878
Method:                 Least Squares   F-statistic:                     5791.
Date:                Mon, 26 Nov 2018   Prob (F-statistic):               0.00
Time:                        23:46:59   Log-Likelihood:                -36688.
No. Observations:                8070   AIC:                         7.340e+04
Df Residuals:                    8059   BIC:                         7.348e+04
Df Model:                          10                                         
Covariance Type:            nonrobust                                         
================================================================================
                   coef    std err          t      P>|t|      [0.025      0.975]
--------------------------------------------------------------------------------
const          121.1046      0.553    219.193      0.000     120.022     122.188
model_year      40.0663      0.351    114.053      0.000      39.378      40.755
distance       -17.3005      0.326    -53.136      0.000     -17.939     -16.662
lowdown         22.0338      1.130     19.507      0.000      19.820      24.248
aero            16.5364      1.067     15.504      0.000      14.446      18.627
repare         -14.6507      0.832    -17.604      0.000     -16.282     -13.019
cruise          10.7129      0.690     15.536      0.000       9.361      12.065
auto_brake      14.4923      0.990     14.633      0.000      12.551      16.434
record_book     -4.2196      0.534     -7.905      0.000      -5.266      -3.173
sheat_heater    24.9746      1.023     24.423      0.000      22.970      26.979
camera           1.2611      0.593      2.126      0.034       0.098       2.424
==============================================================================
Omnibus:                     1373.771   Durbin-Watson:                   1.990
Prob(Omnibus):                  0.000   Jarque-Bera (JB):             4903.483
Skew:                           0.835   Prob(JB):                         0.00
Kurtosis:                       6.434   Cond. No.                         7.58
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

いっぱい数字が出てきて焦る。coefとかはわかるとして、横に続くのはt値、p値、95%信頼区間か。上段はR2, adjusted R2, F値、対数尤度なんかも出してくれている。この辺をすべて理解して使いこなすには もう少し統計の勉強が必要である。statsmodelsの公式ドキュメントはこちら

これを見ると、まあ年式が一番値段に影響してて、次いでヒートシーター、ローダウン、衝突被害軽減ブレーキ、クルーズコントロールか。走行距離と修復歴にはしっかり負の相関が確認できる。 修復歴があるのとないのとでは平均で14.6万円程変わってくるが、これを高いとみるか安いとみるか。修復歴ありの車は骨格やシャーシにダメージがあるほどの事故があった車であり、そういった車はドアの開閉や 直進性など何かと問題が出てくることが多い(某知恵袋から得た知識)。軽微な事故車もあるらしいが、素人には到底見分けはつかないので十数万円ケチって命を乗せる車を買うくらいならおとなしく年式を下げたほうが良い。 まあ調べたらどのサイトでも同じことを言ってるけど、具体的な数字が出せたのはちょっとうれしい。

ただこの結果の中で、定期点検記録簿の有無に負の相関がある理由はわからない。ダミー変数だらけで、車両価格を近似するのに説明変数(というか情報量)が足りなかったとか?迷宮入りだ… 。

まとめ

  • フルモデルチェンジ後の旧モデルは価格が大幅に下落
    プリウスの場合は中央値で約50万円ほど変わる
  • 「〇万km超えたら値段が下がる!」は嘘
  • 修復歴ありとなしでは平均で約15万円の価格差
    修復歴ありはやめといたほうがいい

こうしてみると案外当たり前のことしか発見できてなかったりする。。でも色んな記事を見てると、データ分析をしても新たな知見が得られないことは往々にしてあることらしく、だからこそ本職のデータアナリストの方々は 最初の目的(何を明らかにしたいのか)をしっかり決めるんだろうなあ、と素人ながらに思った。

大変だけど面白そうな仕事で憧れます。おわり。

pathlibとかいう優秀すぎる標準ライブラリ(python)

自分は今までpythonでファイル操作を行うときはos.pathとかglobとかを使っていたが、pathlibという優秀なものがあると聞いて使ってみた。
python3.4以降なら使わない手はない。

以下優れている点

  • os, glob の組み合わせで行ってきた操作が大体pathlib一つでできるようになる
  • メソッドチェーン的に記述でき、可読性が上がる
  • open()などのファイル読み込み系の操作と親和性が高い
  • windowsだとファイルパスにエスケープ二つ\\入れる必要があるが、/区切りで解釈してくれる

以下もうひとつな点

  • 一部ライブラリではPathオブジェクトを受け付けず、str()でキャストする必要がある
  • copyなど一部未対応のメソッドがある

公式ドキュメントはこちら

主な使用方法

import, インスタンス生成

基本はPath オブジェクトを生成して操作することになる。
インスタンス生成時にファイルまたはディレクトリのパスを指定する。(絶対パスでも相対パスでもよい)

>>> from pathlib import Path
>>> p = Path('aaa/bbb/ccc.txt')

pはPathのサブクラスであり、windowsならWindowsPathlinux, maxならPosixPathオブジェクトとなる。
使い方はほぼ同じなので気にする必要はない。

引数を指定しないとカレントディレクトリのリンクが自動的にパスに入る。

>>> Path()
WindowsPath('.')

カレントディレクトリ、ホームディレクトリ取得

# カレントディレクトリ取得
>>> p.cwd()

# ホームディレクトリ取得
>>> p.home()

上記メソッドは新たなパスが格納されたPathオブジェクトを返す。
これに限らず、Pathオブジェクトのメソッドは新たなPathを返すため、文字列を直に操作する必要がなくなる。

また、上記メソッドの出力はインスタンス生成時の引数に依らず、常に同じである。
Pathオブジェクトならいつでもカレント、ホームディレクトリを呼び出せる。

絶対パス取得

まずは絶対パスを取得する。

# シンボリックリンクを解決して絶対パスを返す
>>> p = Path('.')
>>> p.resolve()
WindowsPath('D:/project/path_test')

# なんかこっちでもいけるっぽい
>>> p.absolute()

公式ドキュメントには書いてなかったが、p.absolute()でも同じことができるっぽい。内部では同じメソッドを呼び出しているのだろうか。
コードの中身を見てないのでわかりません。

ついでに絶対パスかどうかの確認

>>> p.is_absolute()
True

絶対パスならTrueを返す。

ファイル名、拡張子の取得

ファイルまでのパスがPathオブジェクトに入っているなら以下でファイル名を取得できる
拡張子や、拡張子を除いた名前部分(stem)を取得することもできる。戻り値は文字列なので注意

# ファイル名取得 戻り値は文字列
>>> p.name
'aaa.txt'

# 拡張子取得  戻り値は文字列
>>> p.suffix
'.txt'

# .tar.gzみたいなのは.suffixes でリスト取得できる
>>> p.suffixes
['.tar', '.gz']

# 拡張子を除いたファイル名を取得 戻り値は文字列
>>> p.stem
'aaa'

ファイル、ディレクトリ削除、権限変更

パスがファイルかディレクトリかでメソッドが変わる。

# ファイル削除
>>> p.unlink()

# ディレクトリ削除 ただし空ディレクトリに限る
>>> p.rmdir()

# 権限変更 ファイル、ディレクトリ問わず適用可能
>>> p.chmod(755)
>>> p.cwd(755)

ディレクトリが空じゃない場合はおとなしくshutil.rmtreeとかを使う。

ディレクトリ取得

便利。

# 親ディレクトリ取得 戻り値はPATHオブジェクト
>>> p.parent

パス結合

これが一番便利かもしれない。
Pathオブジェクトでは/演算子がパスの結合として再定義されている。

# Pathオブジェクトに文字列で除算するとパス結合されたPathオブジェクトが返る
>>> Path('a/b/c') / 'd'
Path('a/b/c/d')

#  windowsでのエスケープ二つと混ぜても同じ結果が得られる
>>> Path('a\\b\\c') / 'd'
WindowsPath('a/b/c/d')

# 一応メソッドもある
>>> Path('a/b/c').joinpath('d')
WindowsPath('a/b/c/d')

ファイル一覧取得、検索

指定したパス内のファイル、ディレクトリを一覧表示・検索できる。
検索に関しては、標準ライブラリのglobと使い方がほぼ同じである。
ただし、いずれもメソッドの戻り値はジェネレータであることに注意する。

# パス内のファイル、ディレクトリ一覧のジェネレータを返す
>>> p.iterdir()
<generator object Path.iterdir at 0x00000197DA6D3BF8>

# パス内の.py ファイルを検索、ジェネレータを返す
>>> p.glob('*.py')
<generator object Path.glob at 0x000002804AEC6C50>

# リストとして取り出したければキャストする
>>> list(p.glob('*.py'))

# パス以下を再帰的に検索したい場合は以下のように指定する
>>> p.glob('**/*.py')

ファイルを開く

Pathオブジェクトにはファイルを開くメソッドも用意されている。
テキストとして開くならこれで問題ない。当然だがパスはファイルが指定されている必要がある。

# ファイルを開く
>>> f = p.open('r')

# 組み込み関数にPathオブジェクトを渡すこともできる
>>> f = open(p, 'r')

注意すべきは、Pathオブジェクトに対応していない外部ライブラリも存在するということ。
ここによると、どうやらopenCVcv2.imreadには対応していないらしい。

そんな時は文字列にキャストしてからライブラリに渡せば問題ない。

>>> p = Path('aaa/bbb/ccc.txt')
# str()でキャストできる  UNIX環境
>>> str(p)
'aaa/bbb/ccc.txt'

# windows 環境でも使える
>>> str(p)
'aaa\\bbb\\ccc.txt'

# .as_posix()でもできるが、windows環境下でも / で区切られる
>>> p.as_posix()
'aaa/bbb/ccc.txt'

as_posix()は / で区切られた文字列が返されるため、windows環境では使用できない。
じゃあwindows用のメソッドがあるかと思いきや用意されていないっぽいので、環境に依存したくなければstr()を使うようにすべきか。

判定系

以下のメソッドはTrueかFalseで判定を返す。

# 絶対パスかどうかを判定
>>> p.is_absolute()

# 現在のパスが与えられたパターンと一致するかどうかを判定 (パターンはglob形式で指定)
>>> p.match('a/*txt')

# パスのファイル、またはディレクトリが存在しているかどうかを判定
>>> p.exists()

# ディレクトリかどうか判定
>>> p.is_dir()

# ファイルかどうか
>>> p.is_file()

# シンボリックリンクかどうか
>>> p.is_symlink()

# Pathオブジェクトが指定するファイルと引数で指定したファイルが同じファイルを参照しているかどうか
# 引数にはPathオブジェクトか文字列をとる
>>> p.samefile(other_path)

ファイル名変更など

# 指定したファイルまたはディレクトリをtgtにリネームする
>>> p.rename(tgt)

# パスの末尾のファイルまたはディレクトリ名をtgtに置き換える
# 存在する場合はリネームする
>>> p.replace(tgt)

MongoDBインストール(windows, ubuntu, macOS)

リレーショナルデータベースも触ったことのない人間がいきなりMongoDBを導入してみたのでメモ。
ついでにインストールはwindows, ubuntu, macからそれぞれ行ってみた。


公式ページには大体書いてる。
2018/9/6時点ではv4.0.2が最新らしい。
Install MongoDB Community Edition — MongoDB Manual
Community EditionとEnterprise Editionの二つがあるが、個人&ローカル利用ならCommunity Editionで事足りるのでそちらを入れる。
Enterpriseはセキュリティとかの機能がサポートされて有料になるらしい。詳しくは公式を。

インストール(windows)

以下のURLにアクセス。
MongoDB Download Center | MongoDB

[Community Server] ->[Windows] とタブを開き、VersionはOSのビットを選択(画面ではWindows 64-bit)
f:id:may46onez:20180906213515p:plain

後はインストーラの指示に従ってインストール。ある意味らくちんかもしれない。

途中でMongoDB Compass Communityというソフトも入れるかどうか聞かれるが、これはクライアントGUIツールと呼ばれるものである。要はGUIでデータベースをいじれる。
f:id:may46onez:20180906221206p:plain

こういうツールの使い方も覚えておくと後々便利かもしれない。
参考↓
dev.classmethod.jp

インストールできたらpathを通す。
[windowsキー] → 「環境変数」で検索し環境変数を編集する
Pathを選択し編集し、MongoDB\Server\4.0\binまでのpathを追加↓
f:id:may46onez:20180908120739p:plain

できたらコマンドプロンプトを起動し、

mongod --version

でバージョン表示されるか確認する。

インストール(ubuntu)

Ubuntu公式リポジトリのmongoDBはバージョンが古く、最新バージョンを取得するには一手間必要である。
公式リポジトリ以外からパッケージを取得するためには、そのリポジトリを認証するための公開鍵をキーチェーンに取り込まなければならない。

まず公開鍵と取り込む

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4

その後ソースリストの作成
ubuntu14.04の場合

echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list

ubuntu16.04の場合

echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list

ubuntu18.04の場合

echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list

あとはリポジトリ更新してインストール

sudo apt update
sudo apt install mongodb-org

ターミナルで

mongod --version

とか打ち込んでバージョン情報が表示されるならインストールできてる。

ちなみに最初に取得した公開鍵のことをGPGキーとか言ったりする。GNU Privacy Guardという暗号化ソフトで生成されるらしいが詳しいことはわからないので気が向いたらまた調べる。
一応参考サイト
概要のみ↓
tech.nikkeibp.co.jp
GPGの使い方とか↓
qiita.com

インストール(mac)

homebrewでさくっと。

brew update
brew install mongodb

らくちんすぎる。

手動で入れたい場合は公式からtarファイルを落とす。
以下URLから[Community Server] → [OSX]と選択してtarファイルをダウンロード。

ターミナルで以下のコマンドをたたき展開。

tar -zxvf mongodb-osx-ssl-x86_64-4.0.2.tgz

展開されたディレクトリを適当な場所に移し、ディレクトリ内の/binにパスを通す。
~/.bashrcに以下を追加。

export PATH=<path-to-mongodb-directory>/bin:$PATH

参考とか

使い方

Karl Seguinj氏のThe Little MongoDB Bookというのが勉強になるらしい。しかも無料。
githubでもtexソースが公開されてる。
GitHub - karlseguin/the-little-mongodb-book: The Little MongoDB Book

各言語への訳が有志で行われており、日本語ではMongoDBの薄い本という邦訳が有名っぽい。もうちょい他に訳あったやろ
この薄い本が検索上位に来ることが多いが、内容が古いらしいので以下の「MongoDBの手引き」のほうが良いかも
github.com

PDFは未作成っぽいのでmarkdownepubで。

困ったときの公式ドキュメント
The MongoDB 4.0 Manual — MongoDB Manual

NoSQLの仕組み的な

以下のスライドが参考になる。

Markdownが書きたいのでAtomとVSCodeを整備して比較する

Markdownを書くための環境設定などをメモする。
普段はVSCodeを使っているが、github開発のAtommarkdownと親和性が高そうなので比べてみた。
環境はmacbook promacOS Sierra 10.12.6

Atom

Atomはデフォルトでmarkdownに対応しており、macならctrl + shift + mでリアルタイムプレビューを表示できる。
それに加えて拡張機能も豊富であり、以下のサイトで色々紹介されている。
qiita.com

とりあえず以下の拡張は便利だった。

markdown-writer

各種ショートカットが追加され、キーバインドなどを設定可能。
便利。この後のtool-bar-markdown-writerがもっと便利。

tool-bar-markdown-writer

markdown編集時に、エディタ上部にtool-barが表示される。
markdownの記法を覚えてなくてもGUIで直感的に扱えるようになる。テーブル整形とかも便利。
tool-barmarkdown-writerのパッケージが必要。

markdown-pdf

Atomはデフォルトでhtml出力には対応しているが、pdfなどで出力するにはこのパッケージが必要。
コード部分なども綺麗に出る。
Atomはプレビューや出力のhtml, pdfのデザインなどがデフォルトで綺麗なとこが最大の利点かもしれない。


以下のはちょっと問題があった。

markdown-scroll-sync

プレビューをスクロールすると自動で該当するテキスト部分までスクロールしてくれる。
機能自体は便利で良いのでが、自分の環境ではなぜか動作しなかった。。
Mac以外なら動くのかもしれない。アプデ待ち。

markdown-toc

目次を自動生成してくれるパッケージ。
トピック名を変更しても上書き保存時に目次も変更してくれるのでよい。
が、これで生成したhtmlやpdfのリンクは機能しない。。意味ねえ。。
当然プレビューのリンクも効いていない。

ググってみて、先人がいたと思ったらなんかちょっと違った。
take-she12.hatenablog.com
日本語はidが生成されなくてリンクが正常にいかないらしいということはわかったが、英語でもリンクされない。
何故なのか・・・。
Atom側の問題なのかもしれない。解決できる気がしないのでとりあえず放置。


と思ったらすごいのがあった。

markdown-preview-enhanced

f:id:may46onez:20180309180023p:plain
プレビュー、TOC作成、プレビューとスクロール同期に加えてlatex数式埋め込みからPDF、html、ebook出力などめっちゃ多機能である。
数式埋め込みなど全ての機能を使うには別途パッケージが必要だが、これを入れるだけでとりあえず

  • スクロール同期プレビュー
  • TOC自動作成
  • html出力(リンク生きてる, ただし日本語はだめ)

の三つは実現できる。もうこれだけでいいね!

PDF出力はpuppeteer, phantomJS, princexmlの三つに対応している。princexmlは有償なのでよくわからない。
残り二つについて試してみた

puppeteer

Nodeのライブラリ。今までNode.jsとかいうのに触れた機会がなく、基礎知識など皆無だがとりあえず使う方法だけメモしておく。
Node.jsについての説明は以下だけ読んだ。
qiita.com
qiita.com

macで導入するにはまずhomebrewを導入する。多分↓のでできる。
qiita.com

その後nodeをインストールする。

brew install node
echo 'export PATH="/usr/local/share/npm/bin:$PATH"' >> ~/.bash_profile

するとnpmが使えるようになっているので、

npm install -g puppeteer

とするとpuppeteerでのPDF出力が可能となる。

ここまでやってから、どうやらnodebrewとやらがあるということに気がついた
mmorley.hatenablog.com
homebrewではNode.jsのバージョン管理ができないらしい。
今はよくわかんないので必要になってからでいいかな・・・

PhantomJS

PhantomJSとはwebkitベースのヘッドレスブラウザということらしい。サーバサイドとかウェブ系のことがわからなさすぎて辛い。。
例によってとりあえず使う環境は整えておく。というよりこれはbrewで入れるだけ。

brew install phantomjs

二通りでPDF出力を試したが、puppeteerはコード部分の背景が白くなってしまい、phantomJSはやたらフォントサイズが大きくなってしまうという結果になった。
どっかをいじれば直せるのかもしれないが、とりあえずデフォルトではAtomのプレビューとPDFが綺麗で個人的には一番好き。


VSCode

VSCodeもデフォルトでmarkdownプレビューに対応している。
しかし自動で分割してくれず、shift + command + Vで同じ領域の別タブにプレビュー→自分で画面分割という手順を踏む必要があり、あまりスマートな感じがしない。
以下のパッケージが非常に役に立つ。

Markdown Preview Enhanced

MPE再び。デフォルトでは微妙なmarkdownプレビューを改善してくれる。
Atomと同じctrl + shift + m (またはcommand + K からの V)でMPE Previewを表示でき、自動で画面分割してくれるので余計な操作はいらない。
もはやこれでほぼ事足りる。


一応こんなのもある。

Markdown PDF

VScodeデフォルトのプレビューをPDF変換する。
上書き保存時に自動変換する機能や、スタイルシートを指定して自分好みのpdfを出力できる機能がついており、拡張性が高い。
ただデフォルトだとVScodeのプレビューのまま出てくる上にフォントサイズが少々大きい。
色々いじりたい人向け。ドキュメントが日本語で読みやすい

Markdown All in One

ショートカット、テーブルフォーマット、アウトライン表示などなど、編集する際に役立つ機能が詰まった拡張。
VScodeでがっつりmarkdownを編集する人にはいいかもしれない。
ただしbold体、italic体をトグルするショートカットがVScode自体のショートカットと重複するので注意が必要。

どっちを使うのか

と、ここまで書いたがVScodeには一つ致命的な欠陥がある。
プレビュー表示した状態で日本語を入力→バックスペースを押すとゴミ文字が入力されるというもの。
以下でこの問題が取り上げられている。
forum.vivaldi.net

qiita.com

macOSだけの問題らしい。日本語入力の際に出る不具合だが、プレビュー表示していない時は問題なく編集できる。謎。
今のところ、少なくともMarkdownをプレビュー見ながら編集したければAtomがいいと思われる。

.bstファイルをいじって著者名表記を変える

bibtexで引用する際に注意すべき点として、

  • 引用順をどうするか
  • 著者名表記をどうするか

がまずあげられる。分野によると思うが、自分がよく使うのは

  • 文献を引用された順にソート
  • 著者名のファーストネームはイニシャル

なので、それに合わせて.bstファイルをいじる方法をメモする。

デフォルトのテンプレ

欧文用ではplain.bst, unsrt.bstを使い、和文にはjplain.bst, junsrt.bstを使う。
plainでは参考文献がアルファベット順に出力し、
unsrtでは引用された順に出力する。

特に設定を書き換えない場合は参考文献を載せたい場所で

\bibliographystyle{plain}
\bibliography{reference.bib}

みたいに書けば良いが、自分でいじった.bstファイルを使う場合はtexソースと同じディレクトリに.bstファイルを置く。

ここではjunsrt.bstをいじって自分用のmyjunsrt.bstをつくる。
/usr/local/texlive/2016/texmf-dist/pbibtex/bst/junsrt.bstを適当にコピー。
jplain.bstも同じ場所にある。バージョンとかは適宜読み換える。
見つからない場合は

find /usr/local/ -name junsrt.bst

とかしたら出てくる。windowsもtexlive/以下にあるんじゃないかな。知らんけど。

書き換え

.bstファイル内では、

FUNCTION {---定義名---} {
    ----定義の内容----
}

という形で各種表記の仕方が定義される。
こんな感じ↓

FUNCTION {format.names}
{ 's :=
  #1 'nameptr :=
  s num.names$ 'numnames :=
  numnames 'namesleft :=
    { namesleft #0 > }

    { s nameptr "{ff}{ll}" format.name$ is.kanji.str$
	{s nameptr "{ff}{ll}" format.name$ 't :=}                        
 	{s nameptr "{ff~}{vv~}{ll}{, jj}" format.name$ 't :=}
      if$

      nameptr #1 >
	{ namesleft #1 >

	    {", " * t * }


	    { t "others" =

 		{ s is.kanji.str$

 			{"$B$[$+(B" * }


 			{", et~al." * }

 		  if$
 		}
 		{ s is.kanji.str$

 		    {", " * t * }



		    { numnames #2 =
 			{" and " * t * }
 			{", and " * t * }
		      if$
		    }


 		   if$
 		}
	      if$
	    }
	  if$
	}
	't
      if$
      nameptr #1 + 'nameptr :=
      namesleft #1 - 'namesleft :=
    }
  while$
}

著者名の出力を変えるにはFUNCTION {format.names}の中身を書き換える。
上記nameptrとか書いているあたりを見る

       {s nameptr "{ff~}{vv~}{ll}{, jj}" format.name$ 't :=}

この部分。
{ff~}{vv~}{ll}{, jj}で著者名の表記を指定する。

  • {f}でファーストネームを頭文字のみ表示、{ff}で全て表示
  • {l}でラストネームを頭文字のみ表示、{ll}で全て表示
  • {v}でミドルネームを頭文字のみ表示、{vv}で全て表示
  • {j}でJr.を頭文字のみ表示、{jj}でJr.全て表示
  • `~`をつけるとそこで改行されなくなる。姓と名で改行されないようにするために使われる。
  • `.`をつけるとその位置にピリオドを表示
  • `, `をつけるとその位置にカンマを表示

で変えられる。
例えばJohn Fitzgerald Kennedyが著者名の時、

  • {f.~}{v.~}{ll}{, jj} で J. F. Kennedy
  • {ff~}{v.~}{l.}{, jj} でJohn F. K.

みたいになる。

【追記 18/2/5】
一行前の

	{s nameptr "{ff}{ll}" format.name$ 't :=}

の部分は日本語表記に使用される。ここを書き換えると漢字の苗字の一文字目だけ表示されたりするのでいじらない。


他にもタイトルや出版年の出力を変えることができるらしい。
少なくとも情報系の学会や研究会ではtexフォーマットが与えられることが多いので使う機会はなさそうだが・・・
自前で作る必要がある場合に役立つかもしれない。

○参考元
bst ファイルのカスタマイズ - Okomeda Net
LaTeXで参考文献の形式を変更する方法(bstファイルの編集) - けつあご日記