Python データ分析

データサイエンス グラフ 日本地図 地図 自然言語処理 Streamlit

気象庁 SSDSE 県オープンデータ

データサイエンス

データサイエンスとは大量のデータを整理、分析することで、問題の解決策を得ることです。そのために統計などの数学的手法や、機械学習などを用います。

データサイエンスに取り組む人をデータサイエンティストと言います。データサイエンティストに必要な能力は統計学・数学などの知識、プログラミングなどのエンジニア能力、ビジネスの問題を見つけ、解決を探る洞察力などです。

何らかの問題に対し、データを収集し、それを統計的に分析し、解決案を導き出します。

データサイエンティストは専門の職種としての需要が増えており、また、一般のビジネスマンでもデータサイエンティスト的な思考・行動が求められています。

PPDAC

データ分析の手順は一般的にPPDACサイクルで行われます。
  1. Problem 問題把握
    どういう問題があるのかを明確にし、仮説を立てます。 例:早生まれはスポーツが苦手なのではないか?
  2. Plan 調査計画
    どのようなデータを集めるか計画を立てます。 例:プロスポーツ選手の生年月日を集める。
  3. Data データ収集
    実際に様々な方法でデータを収集します。
  4. Analysys 分析
    Pythonなどのデータ分析機能でデータを分析します。 例:生まれた月ごとの件数を棒グラフ化
  5. Conclusion 結論 分析した結果から結論を導き出します。
5でうまく解決できなかった場合、再び問題把握から行います。

VSCodeでのJupyter環境構築

Jupyter基本操作

セル内にプログラムを書き、Shift+Enterで実行

print(1+2)

式だけでも表示される

1+2

変数もそのまま書けば表示される

a=3+5
a

※別のセルにも変数の値は受け継がれる

ただし、式が自動表示されるのは最後のみ

1+2
3+5

Pandasの基本

インストール

pip install pandas

データ作成

pandasをインポートし、DataFrameにデータを入れる。

DataFrameは二次元の表で行(index)と列(column)にラベルが付いている。
それぞれのデータはNumPyのndarray型で管理される。

リストのリストからDataFrame生成。

import pandas as pd

import pandas as pd

data = [ 
	["りんご", 100], 
	["みかん", 150] 
]

df = pd.DataFrame(data, index=[1, 2], columns=['商品名', '単価'])
df

辞書から生成

import pandas as pd

data = { 
    "商品名" : ["りんご", "みかん", "いちご"],
    "単価"   : [100,200,150]
}
df = pd.DataFrame(data, index = [1,2,3])
df

CSVの読み込み

サンプルデータの準備

seiseki.csv

読み込み

pandasをインポートし、read_csvで読み込みDataFrameに入れる。

import pandas as pd
df = pd.read_csv("seiseki.csv")
df

csv読み込みし、indexを特定の列にする。

import pandas as pd
df = pd.read_csv("seiseki.csv", index_col="名前")
df

データの概要

行、列がいくつあるか

df.shape # 行,列の表示

データ型

ndarray のデータ型で表示。

df.dtypes
# データ型やnullではないデータの数を表示
df.info()

データの抽出

最初のいくつかを表示

# 最初の5個
df.head()

# 最初の10個
df.head(10)
# スライスでも可能
df[:5]

最後のいくつかを表示

# 最後の5個
df.tail()

# 最後の10個
df.tail(10)
# スライスでも可能
df[-5:]

列や行の取り出し

列は df[列名]、行は df.loc[インデックス名]、または df.iloc[番号]。番号は0から始まる。

# 1列のみ
df["国語"]

# 1行のみ
df.loc["佐藤"]

# 1行のみ
df.iloc[0]

1行または1列の形で取り出したものは一次元のラベル付き配列「Series型」になる。

# 複数列取り出し
df[["国語", "算数"]]

# 列から値を取りだす
df["国語"]["佐藤"]

# 行・列で抽出
df.loc["佐藤"]["国語"]
df.loc["佐藤"][["国語","算数"]]

df.iloc[0]["国語"]
df.iloc[0][["国語","算数"]]

条件で行の抽出

df[df["国語"] >= 70]

# 複数条件(and)
df[(df["国語"] >= 70) & (df["算数"] >= 70)]

# 複数条件(or)
df[(df["国語"] >= 70) | (df["算数"] >= 70)]

# query関数
df.query("国語 >= 70")

# query関数(and)
df.query("国語 >= 70 and 算数 >=70")

# query関数(変数)
ten = 70
df.query("国語 >= @ten")

前行のデータの参照

# 指定行前のデータを参照
df["国語"].shift(1)

# 前の行との差分を求める
df["国語"].diff()

# 前の行との変化の割合を求める
df["国語"].pct_change()

左列のデータの参照

# 左列を参照
df.shift(1, axis=1)

# 左の行との差分を求める
df[["国語","算数","理科","社会"]].diff(axis=1)

# 左の行との変化の割合を求める
df[["国語","算数","理科","社会"]].pct_change(axis=1)

データの操作

変更

df.loc["佐藤","算数"] = 90

# 行ごと変更
df.loc["佐藤"]=["A", "男", 70, 70, 70, 70]

# 列の値を一括変更
df["算数"]=100

# 列の値を一括変更(計算)
df["算数"]=df["国語"]+1

追加

# 列追加
df["数学"]=0

# 行追加
df.loc["坂本"]=["B", "男", 70, 70, 70, 70]

# 列をコピー
df["キー"]=df["クラス"]

# 列を追加して文字列の結合
df["キー"] = df["クラス"].str.cat(df.index)

削除

# 行削除(インデックス)
df.drop(3)

# 行削除(インデックス)
df.drop("山田")

# 列の削除
df.drop("クラス", axis=1)

# 列の削除
df.drop(columns=["クラス"])

列名変更

# 1列変更
df = df.rename(columns={'クラス': '組'})

# 全て変更
df.columns = ["class", "sei", "kokugo", "sansu", "eigo", "shakai"]

データ型

型変換

df[列名].astype(型名)

# 文字を整数に
df["国語"] = df["国語"].astype(int)

# 文字を浮動小数点数に
df["気温"] = df["気温"].astype(float)

日付型変換 df[列名] = pd.to_datetime(df[列名])

df["日付"] = pd.to_datetime(df["日付"])

# 年,月,日 を1つの日付列に
df["日付"] = pd.to_datetime({
    "year":df["年"], 
    "month":df["月"], 
    "day":df["日"]
})

型のメソッド

列全体で文字列のメソッドを使うには、df[列名].str.メソッド名

# 文字列の長さ
df["クラス"].str.len()

列全体の日付のメソッドを使うには、df[列名].dt.メソッド名

# 年のみ
df["日付"].dt.year

# 月と日の列を作る
df["月日"] = df["日付"].dt.strftime("%m/%d")

整列とインデックス

#整列
df.sort_values(["国語"], ascending=False)
df = df.sort_values(["国語"], ascending=False)

#ilocでの行の取り出し
df.iloc[0] #並べ替えたあとの0番目
# 列の並び替え
df.reindex(columns=["国語","社会","理科","算数","性別","クラス"])

# 任意順の行の並び替え
df.reindex(index=["鈴木","佐藤",・・])
# 逆順 (stepを-1に)
df.iloc[::-1]
#行列反転
df.T
#列をインデックスに割り当て
df = df.set_index("列名")

# インデックス割り当て解除
df = df.reset_index()

統計(合計・平均など)

重複データ

#重複無しのデータ取得
df["クラス"].unique()

#出現回数
df["クラス"].value_counts()

統計量

数値のみの項目に絞る場合、numeric_only=True を付ける

df["国語"].count() # 個数
df["国語"].sum() # 合計
df["国語"].mean() # 平均
df["国語"].median() # 中央値
df["国語"].mode() # 最頻値
df["国語"].min() # 最小値
df["国語"].max() # 最大値
df["国語"].var() # 分散
df["国語"].std() # 標準偏差(ばらつきを示す。平均からどれぐらい離れているか)

df["累計"]=df["国語"].cumsum() #累計

四分位数

df["国語"].quantile(0.25) #第一四分位数
df["国語"].quantile(0.50) #第二四分位数 = 中央値
df["国語"].quantile(0.75) #第三四分位数

基本統計量の表示

count、mean、std、min、25%、50% 75% max

df.describe()

相関係数

0.7~ 強い相関あり
0.4~ 相関あり
0.2~ 弱い相関あり

df.corr()

t検定

2つの平均値に有意差があるかどうかを検定する。

scipy.statsのttest_ind関数を使用する

オプションのalternativeは片側検定の場合指定。大きいかどうかなら greater、小さいかどうかなら less。どちらでもいい場合、指定しない。

pが0.05未満なら統計的に有意となる。

dfa = df[df["クラス"]=="A"]["国語"]
dfb = df[df["クラス"]=="B"]["国語"]

from scipy import stats

t, p = stats.ttest_ind(dfa, dfb, equal_var=False, alternative='less')

print(t,p) 

if p < 0.05:
    print('統計的に有意')
else:
    print('統計的に有意ではない')

グループ分け

列によるグループ分け

grp = df.groupby("クラス")
grp["国語"].mean() # クラスごとの国語の平均点
df.groupby("クラス").mean() # クラスごとの全ての平均点

他にも sum(合計)、max(最大)、min(最小)、std(標準偏差)、count(件数)、describe(基本統計量)などが使用できる。

#複数の統計処理
grp.agg(['sum', 'mean', 'std'])

クロス集計

国語をクラス、性別ごとに平均を求める

df.pivot_table(values="国語", index="クラス", columns="性別")

小計・総計も表示

df.pivot_table(values="国語", index="クラス", columns="性別", margins=True)

平均以外の時にはaggfuncで関数を指定する

import numpy as np

# 最大値
df.pivot_table(values="国語", index="クラス", columns="性別",aggfunc=np.max)

数を数えるときにはpd.crosstab が使える

# クラス、性別でカウント
pd.crosstab(df["クラス"],df["性別"])

欠損値

# 欠損値があるかをtrue/falseで表示
df.isnull()

# 欠損値のある列を確認
df.isnull().any(axis=0)

# 欠損値のある行を確認
df.isnull().any(axis=1)

# 欠損値のある行の数を確認
df.isnull().sum()

# 欠損値のある行を削除
df = df.dropna()

# 国語がある行を残し欠損値のある行を削除
df = df.dropna(subset=["国語"])
# 欠損値を0で穴埋め
df["国語"] = df["国語"].fillna(0)

# 欠損値を平均で穴埋め
heikin = df["国語"].mean()
df["国語"] = df["国語"].fillna(heikin)

# 欠損値を一つ前の値で穴埋め
df = df.fillna(method='ffill')

置換

# 文字列の置換 名前の列の"田"を"本"に
df["名前"] = df["名前"].str.replace("田", "本")

# 正規表現での文字列一部置換 名前の列の"田"を"本"に
df["名前"] = df.replace({"名前":{"田", "本"}}, regex=True)
# 全列での置換 80を800に置換
df = df.replace({80:800})

# 数値の置換 国語の列の80を800に置換
df = df.replace({"国語":{80:800}}) 
# 0と1への置き換え
# データがAとBの場合、A列とB列が出来て、それぞれ0と1が入る。
x = pd.get_dummies(df['クラス'])

# 最初の列(A列)を削除
x = pd.get_dummies(df['クラス'], drop_first=True)

# 元のdfに反映するには、連結する
df = pd.concat([df, x], axis=1)
# 数値への置き換え
# データがAとBの場合、Aが0、Bが1のように列を作る
# groupbyでグループ化し、ngroup()でgroupの番号を取得する。
df['クラスID'] = df.groupby(['クラス']).ngroup()

四捨五入

# 全ての列を小数点以下2桁に四捨五入
df = df.round(2)

# 特定の列(例:気温)を小数点以下2桁に四捨五入
df = df.round({'気温': 2})

結合

横方向の結合

同じインデックス同士を結合する

df1 = df[["国語","算数"]]
df2 = df[["理科","社会"]]

df1.join(df2)

同じ列名をキーにして結合する

df = pd.read_csv("seiseki.csv")

df1 = df[["名前","国語","算数"]]
df2 = df[["名前","理科","社会"]]

# 両方に「名前」があるので名前をキーにする
pd.merge(df1,df2)

キーを指定して結合

df = pd.read_csv("seiseki.csv")

df1 = df[["氏名","国語","算数"]]
df2 = df[["名前","理科","社会"]]

pd.merge(df1, df2, left_on='氏名', right_on='名前')

単純結合。concatでaxis=1を指定すると横結合。

df1 = df["国語"]
df2 = df["社会"]

pd.concat([df1,df2], axis=1)

縦方向の結合

concatでaxisを指定しない、またはaxis=0を指定すると縦結合。

df1 = df.query("クラス=='A' and 性別=='男'")
df2 = df.query("クラス=='B' and 性別=='女'")

pd.concat([df1,df2])

順次処理

# インデックスと行
for index,row in df.iterrows():
    print(index,row["国語"])

# 行をリストで
for row in df.values:
    print(row[0])

# 列をリストで
for col in df.columns:
    print(col)

グラフ

グラフ

連関分析

pip install mlxtend

import pandas as pd

# サンプルデータの生成
data = {'ID':[1,2,3,4,5,6],
        'Onion':[1,0,0,1,1,1],
        'Potato':[1,1,0,1,1,1],
        'Burger':[1,1,0,0,1,1],
        'Milk':[0,1,1,1,0,1],
        'Beer':[0,0,1,0,1,0]}
df = pd.DataFrame(data)

以下で分析

df2 = df.drop('ID', axis=1)

from mlxtend.frequent_patterns import apriori
from mlxtend.frequent_patterns import association_rules


# Aprioriの適用 最小サポート0.3
frequent_itemsets = apriori(df2, min_support=0.3, use_colnames=True)

# アソシエーションルールの抽出
rules = association_rules(frequent_itemsets)
rules

各列名の説明

antecedents前提条件(この商品が買われた場合)
consequents結果(前提条件の商品が買われたとき、一緒に買われる商品)
antecedent suppor前提条件のアイテムセットが取引に含まれる割合
consequent support結果のアイテムセットが取引に含まれる割合
supportantecedents と consequents が同時に出現する割合
confidenceantecedents が起きたときに consequents も起きる確率
lift関連性の強さ(1より大きいと正の相関)
representativityルールがデータ全体をどの程度代表しているか
leverage期待される共起頻度との差
convictionルールの確実性(大きいほど信頼できる)

インポート・エクスポート

CSVファイル

#csv読み込み
df = pd.read_csv("seiseki.csv")

#csv書き込み
df.to_csv("out.csv")

#インデックス無し
df.to_csv("out.csv",index=False)

Excelファイル

事前にopenpyxlをインストールする。

pip install openpyxl

#Excel読み込み
df = pd.read_excel("seiseki.xlsx")
df
#Excel書き込み
df.to_excel("out.xlsx")

データベース

read_sql_query で読み込む。

MySQL

import mysql.connector

con = mysql.connector.connect(
    user = 'root',
    password = '',
    database = 'hanbai'
)

import pandas as pd
df = pd.read_sql_query("SELECT * FROM shouhin",con)
df

SQLite

import sqlite3

dbname = 'hanbai.db'
con = sqlite3.connect(dbname)

import pandas as pd
df = pd.read_sql_query("SELECT * FROM shouhin",con)
df

PDFファイル

まずtabulaのインストールを行う。

pip install tabula-py

以下のようにして取得できる。

import pandas as pd
import tabula

dfs = tabula.read_pdf('ファイル名', lattice=True, pages = 'ページ番号')

# このページの最初の表
df = dfs[0]

Web

read_htmlでURLを指定すると、そこにあるテーブルを全てDataTableにし、そのリストを返す。

まず pip install lxml でlxml をインストールしておく。

例としてYahoo!の熊本市のアメダスを読み込む。

import pandas as pd

url = 'https://weather.yahoo.co.jp/weather/amedas/43/86141.html'
dfs = pd.read_html(url)
df = dfs[0]
df

列名の設定

header=0で列名を設定。わかりにくい場合、df.columnsで設定する。

import pandas as pd

url = 'https://weather.yahoo.co.jp/weather/amedas/43/86141.html'
dfs = pd.read_html(url,header=0)
df = dfs[0]

df.columns = ['日付','時刻','気温','降水量','風向','風速','日照時間','積雪深']
df

グラフの表示

import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme(font=["Meiryo"])

df = df.iloc[::-1] # 逆順にする

# グラフ表示
df.plot(x="時刻",y=["気温","日照時間"])
plt.title('熊本市 過去24時間の気温と日照時間')
plt.show()

※欠損がある場合、データ型は文字列になる場合があるので、そのときには数値項目に変換しておく

df["気温"] =  df["気温"].astype("float")
df["日照時間"] =  df["日照時間"].astype("float")