【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 |
| 行ラベルの変更方法
df.index = 新しい行ラベルのリスト
:新しい行ラベルを設定df.reset_index(inplace=True)
:行ラベルを通し番号に再設定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 ~基本操作~
pythonのpandasについて複数回に分けてまとめていきます。
| 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ファイルとして書き出す
pandasのread_csv
で以下のようなcsvファイルをデータフレームとして読み込み、身長、体重からBMIを計算、新しくBMI列を追加しto_csv
で新しくcsvファイルを書き出します。
name | height | weight |
---|---|---|
Sato | 1.68 | 60 |
Sasaki | 1.78 | 54 |
Suzuki | 1.59 | 84 |
diagonal.csv
手順
- 身長、体重のリストを引数にBMIを計算しリストとして返す関数を作成。
- ファイル名を引数に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のリストを作成しそれを返します。
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)
【Python】簡単スクレイピング
今回は前回収集した日系電子版のホームページのHTMLから記事タイトルとその記事のカテゴリーを収集します。
成果物イメージ
ジャンル | タイトル |
---|---|
ネット・IT | Tモバイル-スプリント統合に米司法省が示す懸念 |
ネット・IT | 中国ネット通販2位・京東、廉売合戦で体力失う |
AI | 日立、人工知能を使った工場向けセキュリティー技術 |
上表のようにジャンルと記事タイトルを一対で収集することを最終的に目指します。
使用するモジュール
import requests from bs4 import BeautifulSoup import csv import os
スクレイピングの手順
- webページがどのように構成されてるかをみる。
記事タイトルにカーソルを合わせて右クリックで一番下にある検証をクリックします。
すると画像右のようなページがどうように構成されているかが表示されます。
拡大すると 記事タイトルは
<span class="class_ = 'm-miM09_titleL'> </span>
で囲まれていることがわかります。
スクレイピングの実装
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)
上コードを実行すると見ずらいですが以下のような結果を得ることができます。
かなりおおざっぱですがこれで集めたいデータを無事集めることができました。
スクレイピング部分についてはいずれ細かく見ていきたいと思います。
【Python】簡単クローリング
今回はPythonにより最低限のモジュールでクローリングをしていきます。
使用するモジュール
import requests import os from time import sleep
クローリングで対象ページをhtmlファイルとして取得し、取得したhtmlファイルに対してスクレイピングをしていきます。今回はクローリング部分について書いていきます。 青丸で囲ったカテゴリーごとに指定したページ数分のHTMLを取得し、最終的には記事タイトルと記事のカテゴリーを収集することを目指します。
クローラー作成の手順
- 各カテゴリーのページの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/" - ページごとの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で保存する。
4カテゴリー3ページずつHTMLファイルが保存されている。
まとめ
webページのURLがどのように構成されているかを確認することができればこのようなページ移動のみで達成できるものであれば簡単につくることができます。
次回は取得したHTMLをスクレイピングして記事タイトル等を取得していきます。
【Python】オブジェクト指向プログラミング vol.6 ~継承~
継承
クラスには継承という機能があり、引継ぎすることができます。
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と型と値や関数を同時に保持できる便利なデータ
- オブジェクトを中心にプログラムを作って分割統治する設計手法のこと
クラス
- オブジェクトに与えるテンプレート
- クラスのテンプレートの情報を持って生まれたオブジェクトのこと
継承
- あるクラスから派生して子供のクラスを作れる
- 継承したクラスは、継承元の情報にアクセスできる
- 自分より上位のクラスのことをスーパークラスという