コンテンツにスキップ

Python/Pandas

出典: フリー教科書『ウィキブックス(Wikibooks)』
Wikipedia
Wikipedia
ウィキペディアpandasの記事があります。

Pandasは、Pythonのデータ処理ライブラリであり、データの取り扱いにおいて非常に便利です。Pandasを使用することで、CSVやExcel、JSONなどの様々なデータ形式の読み込みや保存、データの選択やフィルタリング、集約やグループ化、データの前処理、そして機械学習におけるデータの前処理や特徴量エンジニアリングなど、データの取り扱いに関する様々な処理を簡単に行うことができます。

本チュートリアルでは、Pandasの基本的な使い方から、応用的な操作までを網羅的に解説します。初心者から中級者まで、データの取り扱いについて幅広く学ぶことができます。また、機械学習におけるデータの前処理や特徴量エンジニアリングについても詳しく解説し、実践的なスキルを身につけることができます。

このチュートリアルを通じて、Pandasの基本的な機能を理解し、効率的なデータの取り扱いについて学びましょう。

Pandasの概要

[編集]

Pandasとは何か

[編集]

Pandasは、Pythonのデータ処理ライブラリであり、データの取り扱いにおいて非常に便利なライブラリです。 Pandasは、NumPyやSciPyなどの科学計算ライブラリに基づいて開発され、データの取り扱いに関する様々な処理を簡単に行うことができます。 Pandasは、データフレームとシリーズの2つの主要なデータ構造を提供しており、これらのデータ構造を使ってデータの読み込み、加工、変換、集計、可視化などを行うことができます。

Pandasが有用な理由

[編集]

Pandasは、以下のような理由からデータ処理において非常に有用なライブラリです。

  • 多様なデータ形式のサポート:CSV、Excel、JSON、HTML、SQLなど、様々なデータ形式を簡単に読み込んだり、保存したりすることができます。
  • 欠損値や重複データの処理:データの前処理に必要な欠損値や重複データの処理が簡単に行えます。
  • データの集約とグループ化:データの集計やグループ化を容易に行うことができます。
  • 時系列データの処理:時系列データを扱うための便利な機能が多数提供されています。
  • 高度なインデックス機能:行や列のラベルによるインデックス機能が豊富に提供されており、データの選択やフィルタリング、マージなどを行うことができます。

Pandasの基本的な機能

[編集]

Pandasには、以下のような基本的な機能があります。

  • データの読み込みと保存:CSV、Excel、JSON、HTML、SQLなどのデータ形式の読み込みと保存が簡単に行えます。
  • データフレームとシリーズの作成:データフレームとシリーズの2つの主要なデータ構造を提供しており、これらのデータ構造を使ってデータの取り扱いができます。
  • データの選択とフィルタリング:行や列のラベルによるデータの選択やフィルタリングが簡単に行えます。
  • データの変換と集計:データの変換や集計を簡単に行うことができます。例えば、平均、中央値、最大値、最小値などの統計量を計算することができます。
  • データの可視化:Matplotlibと統合されており、データの可視化が簡単に行えます。
  • データの結合と連結:複数のデータフレームを結合したり、データフレームを連結したりすることができます。
  • 時系列データの処理:時系列データを扱うための便利な機能が多数提供されています。
  • データの欠損値や重複データの処理:データの前処理に必要な欠損値や重複データの処理が簡単に行えます。
  • インデックスの操作:行や列のインデックスを操作して、データの選択やフィルタリング、マージなどを行うことができます。Pandasは、複数のインデックスを持つことができるため、複雑なデータ処理も可能です。

これらの機能を駆使することで、データの読み込み、変換、加工、集計、可視化などの作業を簡単に行うことができます。また、Pandasは、Pythonの標準ライブラリであるcsvやsqlite3よりも高速に処理を行うことができるため、大量のデータを扱う場合にも非常に有用です。

データの読み込みと保存

[編集]

Pandasは、CSV、Excel、JSON、HTML、SQLなどのデータ形式の読み込みと保存が簡単に行えます。

CSV、Excel、JSONなどのデータ形式の読み込みと保存

[編集]

CSV、Excel、JSONなどのデータ形式をPandasで読み込むには、read_csv()read_excel()read_json()などの関数を使用します。例えば、以下のようにCSVファイルを読み込むことができます。

import pandas as pd

df = pd.read_csv('data.csv')

また、Pandasを使ってCSVファイルを書き出すことも簡単です。to_csv()メソッドを使用して、データフレームをCSVファイルに書き出すことができます。

df.to_csv('output.csv', index=False)

index=Falseを指定することで、インデックスをCSVファイルに書き出さないようにすることができます。

データベースからのデータの読み込み

[編集]

Pandasは、データベースからのデータの読み込みもサポートしています。read_sql()メソッドを使用して、SQLクエリの結果をデータフレームとして読み込むことができます。

import pandas as pd
import sqlite3

# SQLiteデータベースに接続
conn = sqlite3.connect('example.db')

# SQLクエリを実行してデータを読み込む
df = pd.read_sql('SELECT * FROM table_name', conn)

# データベースを閉じる
conn.close()

上記の例では、example.dbというSQLiteデータベースに接続して、SELECT * FROM table_nameというSQLクエリを実行して、結果をdfという名前のデータフレームに格納しています。table_nameは、データベース内の読み込みたいテーブル名に置き換えてください。

このようにして、データベースからデータを簡単に読み込むことができます。

データの基本的な操作

[編集]

Pandasは、データフレームやシリーズといったデータ構造を使って、データの選択、フィルタリング、集約、ソート、結合などの操作を簡単に行うことができます。

データの選択とフィルタリング

[編集]

データフレームやシリーズから特定の列や行を選択する方法はいくつかあります。以下は、Pandasを使ってデータを読み込み、データフレームから特定の列を選択する例です。

import pandas as pd

# CSVファイルからデータを読み込む
df = pd.read_csv('data.csv')

# 特定の列を選択する
df_column = df['column_name']

また、条件を指定してデータをフィルタリングすることもできます。以下は、Pandasを使ってデータを読み込み、条件を指定してデータをフィルタリングする例です。

import pandas as pd

# CSVファイルからデータを読み込む
df = pd.read_csv('data.csv')

# 条件を指定してデータをフィルタリングする
df_filtered = df[df['column_name'] > 0]

データの集約とグループ化

[編集]

データを集約するためには、groupby()メソッドを使います。以下は、Pandasを使ってデータを読み込み、データをグループ化して平均値を計算する例です。

import pandas as pd

# CSVファイルからデータを読み込む
df = pd.read_csv('data.csv')

# データをグループ化して平均値を計算する
df_grouped = df.groupby('column_name').mean()

データのソートと順序の変更

[編集]

Pandasでは、データフレームの行や列をソートしたり、並び替えたりすることができます。以下に、データのソートと順序の変更に関するいくつかの基本的な操作を示します。

  • データフレームをインデックスまたは列の値に基づいてソートすることができます。これは、 sort_values() メソッドを使用して実現できます。このメソッドには、ソートするためのカラム、ソート順序(昇順または降順)、および欠損値の扱いに関するオプションを指定できます。
  • データフレームの行または列の順序を変更するには、 reindex() メソッドを使用します。このメソッドには、元のインデックスまたは列のラベルを新しい順序で指定することができます。
  • データフレームをランダムに並び替えることができます。これは、 sample() メソッドを使用して実現できます。

データの結合と連結

[編集]

以下は、Pandasでデータのソートと順序の変更を行うための例です。

import pandas as pd

# DataFrameの作成
df = pd.DataFrame({'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Emily'],
                   'Age': [25, 32, 18, 47, 24],
                   'Salary': [50000, 80000, 20000, 100000, 40000]})

# Age列を昇順にソートする
df = df.sort_values(by='Age')

# Salary列を降順にソートする
df = df.sort_values(by='Salary', ascending=False)

# Age列を基準にしてランキングを作成する
df['Rank'] = df['Age'].rank()

print(df)

上記のコードでは、DataFrameを作成し、sort_values()メソッドを使用してAge列とSalary列をソートしています。また、rank()メソッドを使用して、Age列を基準にしたランキングを作成しています。

以下は、Pandasでデータの結合と連結を行うための例です。

import pandas as pd

# 2つのDataFrameの作成
df1 = pd.DataFrame({'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Emily'],
                    'Age': [25, 32, 18, 47, 24]})
df2 = pd.DataFrame({'Name': ['Bob', 'David', 'Frank', 'Grace'],
                    'Salary': [80000, 100000, 30000, 50000]})

# concat関数を使用して、2つのDataFrameを縦方向に連結する
df_concat = pd.concat([df1, df2], axis=0)

# merge関数を使用して、Name列を基準にして2つのDataFrameを横方向に結合する
df_merge = pd.merge(df1, df2, on='Name', how='inner')

print(df_concat)
print(df_merge)

上記のコードでは、2つのDataFrameを作成し、concat()関数を使用して縦方向に連結し、merge()関数を使用してName列を基準にして横方向に結合しています。merge()関数では、onパラメータで結合の基準となる列を指定し、howパラメータで結合方法を指定しています。

データの前処理

[編集]

欠損値の処理

[編集]

欠損値とは、データの中で値が入っていない箇所を指します。Pandasでは、欠損値をNaN(Not a Number)として扱います。欠損値はデータ分析において問題を引き起こすことがあるため、適切な処理が必要です。

例えば、以下のようなデータがあるとします。

import pandas as pd
import numpy as np

df = pd.DataFrame({'A': [1, 2, np.nan], 'B': [4, np.nan, np.nan], 'C': [7, 8, 9]})
print(df)
     A    B  C
0  1.0  4.0  7
1  2.0  NaN  8
2  NaN  NaN  9

このデータに含まれる欠損値を削除するには、dropna()メソッドを使います。

df_dropna = df.dropna()
print(df_dropna)
     A    B  C
0  1.0  4.0  7

また、欠損値を他の値で埋めるには、fillna()メソッドを使います。

df_fillna = df.fillna(0)
print(df_fillna)
     A    B  C
0  1.0  4.0  7
1  2.0  0.0  8
2  0.0  0.0  9

重複データの処理

[編集]

重複したデータを処理する方法について説明します。以下のようなデータがあるとします。

import pandas as pd

data = pd.DataFrame({'id': [1, 2, 3, 3, 4],
                     'name': ['Alice', 'Bob', 'Charlie', 'Charlie', 'David'],
                     'value': [10, 20, 30, 30, 40]})
print(data)
   id     name  value
0   1    Alice     10
1   2      Bob     20
2   3  Charlie     30
3   3  Charlie     30
4   4    David     40

ここで、id列が重複しています。重複した行を削除するには、drop_duplicates()メソッドを使用します。

data = data.drop_duplicates()
print(data)
   id     name  value
0   1    Alice     10
1   2      Bob     20
2   3  Charlie     30
4   4    David     40

drop_duplicates()メソッドは、デフォルトで全ての列が重複している行を削除します。特定の列を指定して重複した行を削除することもできます。

data = pd.DataFrame({'id': [1, 2, 3, 3, 4],
                     'name': ['Alice', 'Bob', 'Charlie', 'Charlie', 'David'],
                     'value': [10, 20, 30, 30, 40]})
data = data.drop_duplicates(subset=['id', 'name'])
print(data)
   id     name  value
0   1    Alice     10
1   2      Bob     20
2   3  Charlie     30
4   4    David     40

データの型の変換

[編集]

データの型を変換する方法について説明します。以下のようなデータがあるとします。

import pandas as pd

data = pd.DataFrame({'id': [1, 2, 3],
                     'name': ['Alice', 'Bob', 'Charlie'],
                     'value': ['10', '20', '30']})
print(data)
   id     name value
0   1    Alice    10
1   2      Bob    20
2   3  Charlie    30

ここで、value列のデータ型を数値に変換するには、astype()メソッドを使用します。

data['value'] = data['value'].astype(int)
print(data)
   id     name  value
0   1    Alice     10
1   2      Bob     20
2   3  Charlie     30

データのスケーリング

[編集]

データのスケーリングは、値の範囲を変更することによって、モデルのトレーニングを改善するためによく使われます。最も一般的なスケーリングの方法には、標準化と最小最大スケーリングがあります。

標準化

[編集]

標準化は、データを平均0、標準偏差1の分布に変換することを指します。

import pandas as pd
from sklearn.preprocessing import StandardScaler

# データの読み込み
df = pd.read_csv('data.csv')

# スケーリングの実行
scaler = StandardScaler()
df_scaled = pd.DataFrame(scaler.fit_transform(df), columns=df.columns)

最小最大スケーリング

[編集]

最小最大スケーリングは、データを0から1の範囲に変換することを指します。

import pandas as pd
from sklearn.preprocessing import MinMaxScaler

# データの読み込み
df = pd.read_csv('data.csv')

# スケーリングの実行
scaler = MinMaxScaler()
df_scaled = pd.DataFrame(scaler.fit_transform(df), columns=df.columns)

データの正規化

[編集]

データの正規化は、データを0から1の範囲にスケールすることで、異なる範囲の値を持つ複数の特徴量を比較しやすくすることができます。Pandasを使ってデータの正規化を行う方法を見ていきましょう。

まず、以下のようなデータフレームを例として使います。

import pandas as pd

df = pd.DataFrame({
    'A': [10, 20, 30, 40, 50],
    'B': [5, 15, 25, 35, 45],
    'C': [2, 4, 6, 8, 10]
})

このデータフレームの各列に対して、以下のように正規化を行うことができます。

# データの正規化
df_norm = (df - df.min()) / (df.max() - df.min())

print(df_norm)
出力
     A    B    C
0  0.0  0.0  0.0
1  0.25  0.25  0.25
2  0.5  0.5  0.5
3  0.75  0.75  0.75
4  1.0  1.0  1.0

このように、各列の最小値を引いて、最大値と最小値の差で割ることで、0から1の範囲に値を正規化することができます。

データの可視化

[編集]

Pandasは、MatplotlibやSeabornなどのライブラリを使ってデータの可視化が簡単に行えます。

Matplotlibを使用した基本的なプロット

[編集]

MatplotlibはPythonで最も広く使われているプロットライブラリの一つであり、Pandasでもサポートされています。以下は、Pandasを使ってデータを読み込み、Matplotlibを使って簡単なプロットを行う例です。

import pandas as pd
import matplotlib.pyplot as plt

# CSVファイルからデータを読み込む
df = pd.read_csv('data.csv')

# 散布図を作成する
plt.scatter(df['x'], df['y'])
plt.title('Scatter plot of x vs. y')
plt.xlabel('x')
plt.ylabel('y')
plt.show()

上記の例では、data.csvというCSVファイルからデータを読み込み、plt.scatter()メソッドを使って散布図を作成しています。df['x']df['y']は、データフレームdfの列を指定しています。

Seabornを使用した高度なプロット

[編集]

Seabornは、Matplotlibの拡張ライブラリであり、より高度なプロットを作成するための機能を提供しています。以下は、Seabornを使ってヒストグラムと散布図を作成する例です。

import pandas as pd
import seaborn as sns

# CSVファイルからデータを読み込む
df = pd.read_csv('data.csv')

# ヒストグラムを作成する
sns.histplot(data=df, x='x', kde=True)
plt.title('Histogram of x')
plt.xlabel('x')
plt.show()

# 散布図を作成する
sns.scatterplot(data=df, x='x', y='y', hue='color', style='shape')
plt.title('Scatter plot of x vs. y')
plt.xlabel('x')
plt.ylabel('y')
plt.show()

上記の例では、data.csvというCSVファイルからデータを読み込み、sns.histplot()メソッドとsns.scatterplot()メソッドを使ってヒストグラムと散布図を作成しています。huestyle引数は、データフレームの列を指定しています。

時系列データの処理

[編集]

DatetimeIndexを使用した日付の処理

[編集]

Pandasは、日付と時刻の処理に最適なライブラリの1つです。DatetimeIndexを使用すると、日付と時刻のインデックスを作成し、データフレーム内の日付と時刻を簡単に処理できます。以下の例では、to_datetime()関数を使用して、文字列を日付と時刻の形式に変換し、DatetimeIndexを作成しています。

import pandas as pd

# 日付の文字列を定義
date_strings = ['2022-01-01 10:00:00', '2022-01-02 11:00:00', '2022-01-03 12:00:00']

# 日付の文字列を日付と時刻の形式に変換し、DatetimeIndexを作成
dates = pd.to_datetime(date_strings)
print(dates)

時系列データのリサンプリング

[編集]

時系列データのリサンプリングは、データポイントをより細かくまたは粗くすることができます。Pandasは、データをより細かくするダウンサンプリングと、データをより粗くするアップサンプリングの両方をサポートしています。

以下の例では、1時間ごとのデータを、3時間ごとのデータにダウンサンプリングしています。この場合、3時間ごとのデータの平均値が計算され、新しい3時間ごとのインデックスが作成されます。

import pandas as pd
import numpy as np

# 日付と時刻を含むインデックスを持つデータフレームを作成
date_rng = pd.date_range(start='1/1/2022', end='1/10/2022', freq='H')
df = pd.DataFrame(date_rng, columns=['date'])
df['data'] = np.random.randint(0,100,size=(len(date_rng)))
df = df.set_index('date')
print(df.head())

# ダウンサンプリング
df_resampled = df.resample('3H').mean()
print(df_resampled.head())

移動平均と指数平滑化

[編集]

移動平均と指数平滑化は、時系列データの平滑化によく使われます。Pandasは、これらの操作を簡単に行える機能を提供しています。

まず、必要なライブラリをインポートします。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

次に、サンプルの時系列データを作成します。

# サンプルの時系列データを作成
dates = pd.date_range('20220101', periods=100)
ts = pd.Series(np.random.randn(100), index=dates)

移動平均は、指定した期間の平均値を求めることで、時系列データを平滑化します。Pandasでは、rolling() メソッドを使用して移動平均を計算できます。以下は、ウィンドウ幅が10の移動平均を計算する例です。

# 移動平均を計算
ma = ts.rolling(window=10).mean()

# グラフにプロット
plt.plot(ts, label='Original')
plt.plot(ma, label='Moving Average')
plt.legend()
plt.show()

指数平滑化は、過去のデータに大きな重みをつけつつ、新しいデータに小さな重みをつけることで、時系列データを平滑化します。Pandasでは、ewm() メソッドを使用して指数平滑化を計算できます。以下は、係数が0.2の指数平滑化を計算する例です。

# 指数平滑化を計算
ewma = ts.ewm(alpha=0.2).mean()

# グラフにプロット
plt.plot(ts, label='Original')
plt.plot(ewma, label='Exponentially Weighted Moving Average')
plt.legend()
plt.show()

これらの方法を使うことで、時系列データの平滑化やトレンドの把握が容易になります。

機械学習におけるPandasの利用

[編集]

データの前処理と特徴量エンジニアリング

[編集]

機械学習において、データの前処理と特徴量エンジニアリングは非常に重要な作業となります。Pandasはデータの前処理や特徴量エンジニアリングを支援するための便利な機能を提供しています。以下に、その一例を示します。

欠損値の処理

[編集]

欠損値は、機械学習において扱いにくい問題の1つです。Pandasは、欠損値を補完したり、除去するための機能を提供しています。

import pandas as pd
import numpy as np

# サンプルデータの作成
df = pd.DataFrame({'A': [1, 2, np.nan, 4],
                   'B': [5, np.nan, 7, 8],
                   'C': [9, 10, 11, 12]})
print(df)

# 欠損値を含む行を除去する
df.dropna(inplace=True)
print(df)

# 欠損値を平均値で補完する
df.fillna(value=df.mean(), inplace=True)
print(df)

カテゴリカルな特徴量の変換

[編集]

機械学習アルゴリズムには、数値データしか扱えないものが多いため、カテゴリカルな特徴量を数値データに変換する必要があります。Pandasは、カテゴリカルな特徴量をダミー変数に変換するためのget_dummies関数を提供しています。

# サンプルデータの作成
df = pd.DataFrame({'A': ['a', 'b', 'c', 'a', 'b', 'c'],
                   'B': [1, 2, 3, 4, 5, 6]})
print(df)

# A列をダミー変数に変換する
dummy = pd.get_dummies(df['A'])
print(dummy)

# 元のデータフレームにダミー変数を結合する
df = pd.concat([df, dummy], axis=1)
print(df)

データの分割と交差検証

[編集]

データの分割

[編集]

データの分割には、train_test_split関数を使用します。以下は、アヤメのデータセットをトレーニングセットとテストセットに分割する例です。

import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

iris = load_iris()
iris_df = pd.DataFrame(data=iris['data'], columns=iris['feature_names'])
iris_df['target'] = iris['target']

# 特徴量とターゲットに分割
X = iris_df.drop('target', axis=1)
y = iris_df['target']

# データの分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

train_test_split関数は、test_size引数でテストセットの割合を指定し、random_state引数でランダムシードを指定します。

交差検証

[編集]

交差検証には、cross_val_score関数を使用します。以下は、アヤメのデータセットを使って、交差検証を行う例です。

import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression

iris = load_iris()
iris_df = pd.DataFrame(data=iris['data'], columns=iris['feature_names'])
iris_df['target'] = iris['target']

# 特徴量とターゲットに分割
X = iris_df.drop('target', axis=1)
y = iris_df['target']

# ロジスティック回帰モデルを作成
logreg = LogisticRegression()

# 交差検証を実行
scores = cross_val_score(logreg, X, y, cv=5)
print('Cross-validated scores:', scores)
print('Average score:', scores.mean())

cross_val_score関数は、交差検証の分割数をcv引数で指定します。また、scoring引数で評価指標を指定することもできます。


附録

[編集]

用語集

[編集]
  1. 機械学習(Machine Learning) データからパターンを発見し、モデルを構築することによって、新しいデータに対して予測を行うことができる、人工知能の一分野。
  2. データサイエンス(Data Science) 大量のデータを収集し、それを分析することによって、ビジネス上の価値を発見し、活用することを目的とした、情報科学の一分野。
  3. データセット(Dataset) 機械学習やデータ分析において使用されるデータの集合。
  4. データフレーム(DataFrame):Pandasパッケージで提供される、表形式のデータ構造。行と列から構成され、列には異なる型のデータを格納できる。
  5. 特徴量(Feature):機械学習において、予測モデルを構築するために利用される入力変数のこと。特徴量の数や種類、どの特徴量を選ぶかが予測モデルの性能に影響を与える。
  6. ラベル(Label) 機械学習において、予測したい対象となるデータのこと。教師あり学習において、正解データとして使用される。
  7. 教師あり学習(Supervised Learning) 特定の入力に対して正解データが与えられ、そのデータを元にモデルを学習させる方法。
  8. 教師なし学習(Unsupervised Learning) 正解データが与えられない状態で、データの特徴を把握し、分類やクラスタリングを行う方法。
  9. 検証データ(Validation Data) モデルの性能を評価するために使用される、学習には用いないデータ。
  10. 交差検証(Cross-validation):機械学習において、データセットを複数の部分に分割し、それらを順番に評価に用いる方法。交差検証によって、過学習や汎化性能の低下を防ぐことができる。
  11. 汎化性能(Generalization Performance) 学習済みモデルが、未知のデータに対して正しい予測を行う能力のこと。
  12. 過学習(Overfitting) 学習データに対しては高い性能を発揮するが、未知のデータに対しては性能が低下する現象。
  13. 欠損値(Missing Value):データセット中に存在する値が欠損していることを指す。欠損値を含むデータに対しては、欠損値の処理が必要になる。
  14. 目的変数(Target Variable):機械学習において、予測対象となる変数のこと。目的変数を予測するために、特徴量を用いた予測モデルを構築する。
  15. モデル選択(Model Selection):機械学習において、どのような予測モデルを選択するか決定すること。モデルの性能や利用可能なデータの量、モデルのパラメータなどが考慮される。
  16. モデル評価(Model Evaluation):機械学習において、構築した予測モデルの性能を評価すること。一般的な評価指標には、精度(Accuracy)、再現率(Recall)、適合率(Precision)、F1スコア(F1-score)などがある。
  17. 正則化(Regularization):機械学習において、過学習を防ぐために行われる手法の一つ。正則化を行うことで、モデルの複雑さを調整し、汎化性能を向上させる。
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy