Diagonal M

データサイエンス系長期インターンで学んだことなど

【Python】pandas基礎vol.3 ~DataFrame~

| Pandasのデータ構造(DataFrame)

pandasでよく使われるデータ構造として1次元のSeries、2次元のDataFrameがある。

| DataFrame(データフレーム)

Seriesは1次元だったが、DataFrameは2次元のデータ構造である。

列と行にそれぞれにラベルを持っており、それぞれを列ラベル、行ラベルと呼ぶ。

<DataFrameの作成>

import pandas as pd

df = pd.DataFrame([list('abcd'), list('efgh')])
df
0 1 2 3
0 a b c d
1 e f g h

行ごとにリストを指定し、DataFrameを作成した。

データフレーム初期化時に、データとして2次元のリストだけを渡すと、列ラベルと行ラベルには0から始まる連番が設定される。

<DataFrameのデータに設定されている値を確認する>

df_values = df.values
df_values


# array([['a', 'b', 'c', 'd'],
#        ['e', 'f', 'g', 'h']], dtype=object)

<DataFrameの列ラベルを確認する>

df_columns = df.columns
df_columns

# RangeIndex(start=0, stop=4, step=1)

<DataFrameの行ラベルに設定されている値を確認する>

df_index = df.index
df_index


# RangeIndex(start=0, stop=2, step=1)

行ラベルはdf.index、列ラベルはdf.columnsでアクセスできる。

| DataFrameの列ラベルの変更と列の取得

列の値を参照する時は、辞書のようなアクセスdf[列ラベル]かプロパティーによるアクセスdf.列ラベルが可能である。参照結果はSeriesとして返される。

<列ラベルの変更>

df.columns=list('ABCD')
df
A B C D
0 a b c d
1 e f g h

<列ラベルで参照>

df['A']


# 0    a
# 1    e
# Name: A, dtype: object

<リストして表示>

df['A'].tolist()


# ['a', 'e']

| 行ラベルでのデータ参照

行ごとにデータを参照したい場合はDataFrame.loc,DataFrame.ilocを使う。

列ラベルを指定して列を取得したときと同様にシリーズを取得できる。

df_loc = df.loc[行ラベル]と指定すると、指定した行ラベルが対応している行のデータがシリーズとして返される。df.loc[1]は2行目のデータを指定している。

<データフレームの作成>

import pandas as pd

# DataFrameの作成
df = pd.DataFrame(
    [list('abcd'), list('efgh'),
     list('ijkl'), list('mnop')],
    columns=['A', 'B', 'C', 'D'])
df
A B C D
0 a b c d
1 e f g h
2 i j k l
3 m n o p

<2行目の取得>

df_loc = df.loc[1]
df_loc


# A    e
# B    f
# C    g
# D    h
# Name: 1, dtype: object

<リストとして取得>

df_loc = df.loc[1].tolist()
df_loc


# ['e', 'f', 'g', 'h']

| 行ラベルの設定

行ラベルはデフォルトで0, 1, 2, 3と0から始まる数字が設定が設定される。

行ラベルは任意の値を設定できる。

<行ラベルに'first', 'second', 'third', 'forth'を設定する>

import pandas as pd

# 行ラベルをつける
df = pd.DataFrame(
    [list('abcd'), list('efgh'),
     list('ijkl'), list('mnop')],
    index=['first', 'second', 'third', 'fourth'],
    columns=list('ABCD'))
df
A B C D
first a b c d
second e f g h
third i j k l
fourth m n o p

| 行ラベルの変更方法

  1. df.index = 新しい行ラベルのリスト:新しい行ラベルを設定
  2. df.reset_index(inplace=True):行ラベルを通し番号に再設定
  3. df.set_index(インデックスにしたい列名, inplace=True):指定した列を行ラベルに設定し、列から削除

| 特定データ参照

行と列のラベルを指定することで特定のデータを取得できる

`DataFrame.loc[行ラベル, 列ラベル] `で指定できる。

<gの値を取得する>

df_loc_rowcol = df.loc['second', 'C']
df_loc_rowcol


# 'g'

【Python】pandas基礎vol.2 ~Series~

| pandasのデータ構造(Series)

pandasでよく使われるデータ構造として1次元のSeries、2次元のDataFrameがある。

| Series(シリーズ)

Seriesリストのような順序を持ったデータ列と軸ラベルを格納するオブジェクトである。

軸ラベルは、データが何かを示す見出しで、軸ラベルはインデックスともいい、indexというプロパティで取得できる。

<リストをseriesに変換する>

import pandas as pd

sample_list = ['a', 'b', 'c', 'd']
series = pd.Series(sample_list)
print(series)


# 0    a
# 1    b
# 2    c
# 3    d
# dtype: object

pd.Series(リスト)を利用してSeriesを作成した。

a b c dの部分がSeriesの部分、0 1 2 3の部分が軸ラベルと呼ばれるデータラベル

| 軸ラベルをキーに値を取得

Seriesの軸ラベルの初期値は0から始まる連番が設定されています。

単一の値を参照するときは軸ラベルをキーに指定する。

<seriesの2番目の値(軸ラベル1)の値を取り出す>

series_1 = series[1]
series_1


# 'b'

Seriesの軸ラベルはリストのインデックスや辞書のキーと同じように扱える。

| Seriesの軸ラベルを変更

series.indexにリストを代入することによって、軸ラベルを変更できる。

<軸ラベルの変更>

series.index = ['i1', 'i2', 'i3', 'i4']
series['i2']


# 'b'

| Seriesの値と軸ラベルを取得する

Seriesに格納された値(Numpyの1次元配列)の一覧はSeries.valuesで取得できる。また、軸ラベルの一覧はSeries.index.valuesを使って取得できる。

<値と軸ラベルを取得>

s_values = series.values
s_indexes = series.index.values
print(s_values)
print(s_indexes)


# ['a' 'b' 'c' 'd']
# ['i1' 'i2' 'i3' 'i4']

| Seriesの値をリストとして取得する

series.indexで取得できるものはリストではありません。

<seriesの値をリストとして取得>

s_values_list = series.values.tolist()
s_values_list


# ['a', 'b', 'c', 'd']

【Python】pandas基礎vol.1 ~基本操作~

pythonpandasについて複数回に分けてまとめていきます。

| pandas

データの読み込み、集計、加工、絞り込みなどができ、データ分析のための前処理で役立つ。

主にSeries(1次元:リストのような形式)とDataFrame (2次元:表のような形式)というデータ構造を利用する。

| pandasの主な機能

  • 欠損値の取り扱い
  • ラベル位置を自動的・明示的に揃えたデータの作成
  • データ集約
  • SQLのような問い合わせ、データ加工、データフレーム結合
  • ファイルやデータベースからのデータの取り込みDataFrameに変換
  • 時系列データ固有の処理

| データフレームの作成

Name Age Sex
佐々木 13
杉田 65
後藤 22

<上表のデータフレームを作成する>

import pandas as pd

df = pd.DataFrame([['佐々木', 13, '女'], ['杉田', 65, '男'], ['後藤', 22, '男']],
                  columns=['Name', 'Age', 'Sex', ])
df
Name Age Sex
0 佐々木 13
1 杉田 65
2 後藤 22

データフレームは、列名をキー、列の内容が値の辞書のようなかたちでつくることができる。

出力されたDataFrameオブジェクトの左端には、行番号のようなものが見えます。この「列ではない数値の並び」が、DataFrameオブジェクトの「インデックスラベル」です。インデックスラベルは「列の名前」のようなものであるが、列ではなく行に付きます。pandasは、デフォルトでインデックスラベルに行番号を生成します(0からの連番)。

| 特定の列を取り出す

<名前(Name)列のデータを取り出す>

names = df['Name']
print(names)


# 0    佐々木
# 1     杉田
# 2     後藤
# Name: Name, dtype: object

| 列の最小値、平均値を取り出す

抽出した列の値の最小値、平均値を取得する。

ages = df['Age']

min_age = ages.min()
mean_age = ages.mean()

print(min_age)
print(mean_age)


# 13
# 33.333333333336

その外にも以下のようなものがある。

コード
最小値 min()
最大値 max()
合計値 sum()
平均値 mean()

| pandasによるデータの読み込み

pandas.read_ファイル形式()のようにread_のあとに、ファイル形式にしたがった名前を指定することで、ファイルを読み込みDataFrameに変換できる

対応しているファイル形式の一部を下にまとめます。

ファイル形式 read_< >
Excel read_excel
テキストファイル read_table
HTMLのtableタグ read_html
JSON read_json
リレーショナルデータベース read_sql
Google BigQuery read_gbq

| CSVファイルを取り込む

<diagonal.csvをデータフレームとして読み込む>

df = pd.read_csv('diagonal.csv', encoding='utf-8')
df.head()  # 先頭の5行のみ
city name age sex height weight saving
0 Yokohama Sato 23 man 1.75 60 20
1 Setagaya Sasaki 54 man 1.78 54 800
2 Shibuya Suzuki 17 woman 1.59 84 8
3 Shibuya Goto 22 man 1.74 95 16
4 Yokohama Shimazaki 43 woman 1.55 48 300

| 出現回数を取得する 

<市ごとの人数を集計する>

city_counts = df['city'].value_counts()
city_counts


# Yokohama    4
# Shibuya     3
# Setagaya    3
# Name: city, dtype: int64

| グループごとに集計する

DataFrame.groupby()を利用すると、グループごとの合計値、平均などを集計できる。

市ごとの平均貯金額を計算するには

  • df.groupby(by=列ラベル)で列ラベルで指定した列の値によってグループ化する
  • 合計値sum()や平均値mean()の計算したいものを追記する

<市ごとの平均貯金額を取得する>

grouped_df = df.groupby(by='city').mean()
mean_saving = grouped_df['saving']
mean_saving


# city
# Setagaya    1700.000000
# Shibuya      334.666667
# Yokohama      94.500000
# Name: saving, dtype: float64

| 列の追加

<体重と身長からBMIを計算し列を追加する>

df['bmi'] = df['weight'] / df['height'] ** 2
df.head()
city name age sex height weight saving bmi
0 Yokohama Sato 23 man 1.75 60 20 19.6
1 Setagaya Sasaki 54 man 1.78 54 800 17.0
2 Shibuya Suzuki 17 woman 1.59 84 8 33.2
3 Shibuya Goto 22 man 1.74 95 16 31.4
4 Yokohama Shimazaki 43 woman 1.55 48 300 20.0

| 条件での絞り込み

データフレーム[条件式]と書くことでデータを絞り込むことができます。

<貯金額が1000万円以上の人を表示する>

df_filtering = df[df['saving'] >= 1000]
df_filtering
city name age sex height weight saving bmi
8 Setagaya ota 54 woman 1.49 46 2500 20.7
9 Setagaya kimura 30 man 1.68 60 1800 21.3

| 論理演算での絞り込み

<貯金額が30万円以上か、10万円未満の人を表示する>

df_filtering = df[(df['saving'] >= 1000) | (df['saving'] < 10)]
df_filtering
city name age sex height weight saving bmi
2 Shibuya Suzuki 17 woman 1.59 84 8 33.2
8 Setagaya ota 54 woman 1.49 46 2500 20.7
9 Setagaya kimura 30 man 1.68 60 1800 21.3

論理演算はPythonと違い、andの場合は&、orの場合は|、notの場合は~を利用します

【python】pandasで読み込んだcsvファイルのデータを加工、追加し新しいcsvファイルとして書き出す

pandasread_csvで以下のようなcsvファイルをデータフレームとして読み込み、身長、体重からBMIを計算、新しくBMI列を追加しto_csvで新しくcsvファイルを書き出します。

name height weight
Sato 1.68 60
Sasaki 1.78 54
Suzuki 1.59 84

​ diagonal.csv

手順

  1. 身長、体重のリストを引数にBMIを計算しリストとして返す関数を作成。
  2. ファイル名を引数にcsvファイルを読み込み記録されている、身長、体重で1で作った関数を用いてBMIを計算、BMI列を新たに追加し新しいcsvファイルとして書き出す関数を作成
import pandas as pd


def cal_bmi(height_list: list, weight_list: list) -> list:
    """
    BMIを計算しリストとして返す関数

    @param height_list: 身長のリスト
    @param weight_list: 体重のリスト
    @return: bmiのリスト
    """
    bmi_list = list()
    
    for height, weight in zip(height_list, weight_list):
        bmi = weight / (height ** 2)
        bmi_list.append(bmi)

    return bmi_list


def r_w_csv(filename: str) -> None:
    """
    diagonal.csvファイルをデータフレームとして読み込み身長、体重からBMIを計算
    BMI列を追加し、BMI.CSVファイルを書き出す。

    @param filename: csvファイル名
    """
    
    df = pd.read_csv(filename, encoding='utf-8')
    height_list = df['height'].tolist()  # height列をリストとして取得
    weight_list = df['weight'].tolist()  # weight列をリストとして取得

    bmi_list = cal_bmi(height_list, weight_list)  # BMIを計算しリストとして返す関数

    df['bmi'] = bmi_list
    df.to_csv('bmi.csv', index=False)


if __name__=='__main__':
    r_w_csv('diagonal.csv')

上のコードを実行すると以下のようなcsvファイルが新たに作成されます。

name height weight bmi
Sato 1.68 60 21.2585034
Sasaki 1.78 54 17.04330261
Suzuki 1.59 84 33.22653376

コード解説

BMIを計算しリストとして返す関数

def cal_bmi(height_list: list, weight_list: list) -> list:
    """
    BMIを計算しリストとして返す関数
  @param height_list: 身長のリスト
  @param weight_list: 体重のリスト
  @return: bmiのリスト
  """
    bmi_list = list()

    for height, weight in zip(height_list, weight_list):
        bmi = weight / (height ** 2)
        bmi_list.append(bmi)

    return bmi_list

身長のリストと体重のリストをfor文で回しそれぞれ計算し、bmiのリストを作成しそれを返します。

csvファイル名を引数に新しいcsvファイル作成する関数

def result_bmi(filename: str) -> None:
    """
    diagonal.csvファイルをデータフレームとして読み込み身長、体重からBMIを計算
    BMI列を追加し、BMI.CSVファイルを書き出す。

    @param filename: csvファイル名
    """
       
    df = pd.read_csv(filename, encoding='utf-8')
    height_list = df['height'].tolist()  # height列をリストとして取得
    weight_list = df['weight'].tolist()  # weight列をリストとして取得

    bmi_list = cal_bmi(height_list, weight_list)  # BMIを計算しリストとして返す関数

    df['bmi'] = bmi_list
    df.to_csv('bmi.csv', index=False)
  • pd.read_csvcsvファイルをデータフレームとして読み込む
  • df['列名'].tolist()で当該列のデータをリスト型で読み込む
  • 身長と体重のリストを引数にcal_bmi関数を実行し、bmiのリストを得る
  • データフレームにbmi列を追加しbmi_listのデータを与える
  • df.to_csvbmi.csvとして書き出す

【Python】簡単スクレイピング

今回は前回収集した日系電子版のホームページのHTMLから記事タイトルとその記事のカテゴリーを収集します。

成果物イメージ

ジャンル タイトル
ネット・IT Tモバイル-スプリント統合に米司法省が示す懸念
ネット・IT 中国ネット通販2位・京東、廉売合戦で体力失う
AI 日立、人工知能を使った工場向けセキュリティー技術

上表のようにジャンルと記事タイトルを一対で収集することを最終的に目指します。

使用するモジュール

import requests
from bs4 import BeautifulSoup
import csv
import os

スクレイピングの手順

  1. webページがどのように構成されてるかをみる。

f:id:diagonal-m:20190718172357p:plain

 記事タイトルにカーソルを合わせて右クリックで一番下にある検証をクリックします。

 すると画像右のようなページがどうように構成されているかが表示されます。

 拡大すると f:id:diagonal-m:20190718172425p:plain 記事タイトルは

<span class="class_ = 'm-miM09_titleL'> </span>

で囲まれていることがわかります。

スクレイピングの実装

pythonスクレイピングの実装をしていきます。

nikkei = [] # 記事ジャンル、記事タイトルの空のリスト

# htmlファイルの総数(ループ回数)
num = len(os.listdir('nikkei'))

記事ジャンル、タイトルを格納する空のリストをつくります。

nikkeiフォルダ内のhtmlファイル数をループの回数としてnumに代入します。

# スクレイピング
page = 1
for _ in range(num):
    filepath = os.path.join('nikkei', 'page' + str(page) + '.html')
    with open(filepath , encoding = 'utf-8') as f:
        html = f.read()
        soup = BeautifulSoup(html, "html.parser")
        page = page + 1

        nikkei_list = soup.find_all('div', class_ = 'm-miM09', reversed=False)

        for kiji in nikkei_list:

            title = kiji.find('span', class_ = 'm-miM09_titleL')
            title_text = title.text

            junle_list = kiji.find_all('div', class_ = 'm-miM09_keyword', reversed=False)

            for junle in junle_list:
                junle = junle.find('a')
                if junle == None:
                    keyword = '未分類'
                else:
                    keyword = junle.text

                nikkei.append([keyword, title_text])

with open('nikkei.txt','w',encoding = "utf-8") as f:
    writer = csv.writer(f, lineterminator = "\n")
    writer.writerows([['ジャンル','タイトル']])
    writer.writerows(nikkei)

上コードを実行すると見ずらいですが以下のような結果を得ることができます。

f:id:diagonal-m:20190718172455p:plain

かなりおおざっぱですがこれで集めたいデータを無事集めることができました。

スクレイピング部分についてはいずれ細かく見ていきたいと思います。

【Python】簡単クローリング

今回はPythonにより最低限のモジュールでクローリングをしていきます。
使用するモジュール

import requests
import os
from time import sleep

クローリングで対象ページをhtmlファイルとして取得し、取得したhtmlファイルに対してスクレイピングをしていきます。今回はクローリング部分について書いていきます。

f:id:diagonal-m:20190619145622p:plain
日本経済新聞
青丸で囲ったカテゴリーごとに指定したページ数分のHTMLを取得し、最終的には記事タイトルと記事のカテゴリーを収集することを目指します。

クローラー作成の手順


  1. 各カテゴリーのページのURLがどのようになっているのかを確認する。
    経済・政治 : https://www.nikkei.com/economy/archive/
    ビジネス : https://www.nikkei.com/business/archive/
    テクノロジー : https://www.nikkei.com/technology/archive/
    国際・アジア : https://www.nikkei.com/international/archive/
    上の4つのURLを確認すると以下の部分が共通していることがわかります。 "https://www.nikkei.com/‥‥/archive/"
  2. ページごとのURLがどのようになっているのかを確認する。
    経済・政治のページで1ページ目、2ページ目、3ページ目のURLがどのように変化しているのかを確認する。
    1ページ目 : https://www.nikkei.com/economy/archive/https://www.nikkei.com/economy/archive/?bn=01でも同ページが表示される)
    2ページ目 : https://www.nikkei.com/economy/archive/?bn=11
    3ページ目 : https://www.nikkei.com/economy/archive/?bn=21
    ?bn=に続く数字を変えていけば各ページのHTMLを取得することができそうです。

クローリングの実装


# htmlを格納するディレクトリを作成
file_path = 'nikkei'
if not os.path.exists(file_path):
    os.makedirs(file_path)

# 共通URL
url_org = 'https://www.nikkei.com'

# 政治・経済/ビジネス/テクノロジー/国際・アジア
archive_list = ['economy', 'business', 'technology', 'international']

page = 0
for archive in archive_list:
    for _ in range(3):
        response = requests.get(url_org + '/' + archive + '/' + 'archive/' + \  
                    '?bn=' + str(page) + '1')
        response.encoding = response.apparent_encoding
        # nikkeiディレクトリにpage〇.htmlで保存
        with open(os.path.join('nikkei', 'page' + str(page + 1) + '.html'), 'w', \                   encoding = 'utf-8') as f:
            f.write(response.text)
            page = page + 1
            sleep(1)
  • if not os.path.exists(file_path):file_path(ディレクトリ名)同名のディレクトリが存在しないとき。

  • os.makedirs(file_path)<file_path>ディレクトリを作成する。

  • for archive in archive_list:でカテゴリーごとにfor文を回す。

  • for _ in range(3):カテゴリーごとに3ページずつ取得

  • requests.get(url_org + '/' + archive + '/' + 'archive/' + '?bn=' + str(page) + '1') でカテゴリー、ページごとのURLのHTMLを取得する。

  • response.encoding = response.apparent_encodingで極力文字化けが起こらないようにできる。

取得したhtmlを最初に作成したnikkeiディレクトリにpage〇.htmlで保存する。

f:id:diagonal-m:20190619153353p:plain
nikkeiディレクト
4カテゴリー3ページずつHTMLファイルが保存されている。

まとめ

webページのURLがどのように構成されているかを確認することができればこのようなページ移動のみで達成できるものであれば簡単につくることができます。
 次回は取得したHTMLをスクレイピングして記事タイトル等を取得していきます。

【Python】オブジェクト指向プログラミング vol.6 ~継承~

Pythonオブジェクト指向の勉強の続きです。

継承

クラスには継承という機能があり、引継ぎすることができます。

class Diagonal:
    name = 'Class Diagonal'

class M(Diagonal):
    pass

m = M()
print(m.name)

これを実行すると以下のようになります

Class Diagonal

'Class Diagonal'という文字が表示されました。Class Mには、nameの変数宣言はありません。しかし、継承することでDiagonal クラスで定義された変数にMクラスがアクセスできるようになります。

クラスの宣言のところをみると、class M(Diagonal )とクラス名の隣に括弧で別のクラス名が入っています。MクラスはDiagonal クラスを継承しますという意味で、継承されるクラス(Diagonal クラス)のことをスーパークラスといいます。

オーバーライド

親クラスのメソッドと同じメソッドを子クラスで定義することを、オーバーライドと言います。要は上書きのことです。変数も上書きできれば関数も上書きできます。

class Diagonal:
    def hello(self):
        print('Hello Diagonal')
        
class M:
    def hello(self):
        print('Hello M')

diagonal = Diagonal()
m = M()
diagonal.hello()
m.hello()

DiagonalクラスとMクラスには同じhelloという名前の関数があります。名前が重複しているため、どちらのクラスに作られたhello関数を呼びだすかを指定する必要があります。上のコードを実行すると以下のようになります。

Hello Diagonal
Hello M

まとめ

オブジェクトとは

  • IDと型と値や関数を同時に保持できる便利なデータ

オブジェクト指向

  • オブジェクトを中心にプログラムを作って分割統治する設計手法のこと

クラス

  • オブジェクトに与えるテンプレート

インスタンス

  • クラスのテンプレートの情報を持って生まれたオブジェクトのこと

継承

  • あるクラスから派生して子供のクラスを作れる
  • 継承したクラスは、継承元の情報にアクセスできる
  • 自分より上位のクラスのことをスーパークラスという