こんにちは、zawato(@zawato7)です!
普段はPythonでデータ分析をしている私ですが、最近Lispを勉強し始めました。
せっかくなので、Lispの学習を兼ねてデータ分析をLispで実装してみようと思います。
このシリーズでは、Pythonでのデータ分析経験を活かしながら、同等の処理をLispでどう実装するかを探っていきます。
第1回となる今回は、環境のセットアップとCSVファイルの読み込みを実装します。
Pythonでは、
pandas.read_csv()一行で済む処理を、Lispではどのように実装するのか、そしてその過程でLispの特徴をどう活かせるのかを見ていきましょう。
環境のセットアップ
このシリーズでは、Common Lispの実装の一つであるSBCL (Steel Bank Common Lisp) を使用します。
環境情報
- OS: macOS (またはLinux/Windows)
- SBCL: バージョン2.3.0以上
- Quicklisp: ライブラリ管理ツール
SBCLのインストール
macOSの場合、Homebrewを使って簡単にインストールできます。
brew install sbclUbuntuの場合
sudo apt-get install sbclWindowsの場合は、公式サイトからインストーラをダウンロードしてください。
Quicklispのインストール
Quicklispは、Common Lispのライブラリ管理システムです。以下の手順でインストールします:
1. Quicklispのインストールスクリプトをダウンロード
curl -O https://beta.quicklisp.org/quicklisp.lisp2. SBCLを起動してQuicklispをロード
sbcl --load quicklisp.lisp3. SBCL内で以下を実行
```lisp
(quicklisp-quickstart:install)
(ql:add-to-init-file)
(quit)
```
動作確認
SBCLが正しくインストールされたか確認するために、簡単なHello Worldプログラムを実行してみましょう。
```lisp
(defun hello-world ()
(format t "Hello World!~%"))
(hello-world)
```このコードをファイル(例:hello.lisp)に保存し、以下のコマンドで実行します:
sbcl --script hello.lispまたは、SBCLを対話モードで起動し、コードを直接入力することもできます:
sbcl
* (defun hello-world ()
(format t "Hello World!~%"))
* (hello-world)「Hello World!」と表示されれば、環境のセットアップは完了です。
CSVファイルの読み込み
Pythonでは、pandasを使って
df = pd.read_csv('data.csv')のように簡単にCSVファイルを読み込めますが、Lispではどうでしょうか?
普段Pythonでデータ分析をしている身としては、できるだけPythonに近い使い勝手を実現したいところです。
サンプルデータの準備
まずは分析用の簡単なCSVファイルを作成します。
身長と体重のデータを含む`height_weight.csv`を作成しましょう。
| id | height | weight |
|---|---|---|
| 1 | 170 | 65 |
| 2 | 175 | 70 |
| 3 | 165 | 60 |
| 4 | 180 | 80 |
| 5 | 160 | 55 |
Pythonライクなread_csv関数の実装
Common Lispには標準でCSV読み込み機能がないため、自分で実装する必要があります。
Pythonのpandasに近い使い勝手を目指して、以下のようなCSVパーサーを実装してみました。
```lisp
;;; read_csv.lisp - Pythonのpandas.read_csvに相当する機能の実装
;; 文字列をカンマで分割する関数
(defun split-by-comma (line)
(loop for i = 0 then (1+ j)
for j = (position #\, line :start i)
collect (subseq line i j)
until (null j)))
;; CSVファイルを読み込み、リストのリストとして返す関数
(defun read-csv (filename)
"Pythonのpd.read_csvに相当する基本機能"
(with-open-file (stream filename :direction :input)
(loop for line = (read-line stream nil nil)
while line
collect (split-by-comma line))))
;; CSVファイルを読み込み、ヘッダーと本体に分けて返す関数
(defun read-csv-with-header (filename)
"ヘッダー付きCSVファイルを読み込む"
(let* ((all-data (read-csv filename))
(header (first all-data))
(body (rest all-data)))
(values header body)))
``````lisp
;; 使用例
(defun demo-basic-csv ()
(format t "CSV読み込みテスト:~%")
(multiple-value-bind (header data)
(read-csv-with-header "height_weight.csv")
(format t "ヘッダー: ~A~%" header)
(format t "データ:~%")
(dolist (row data)
(format t " ~A~%" row))))
```このコードを read_csv.lisp として保存し、以下のコマンドで実行できます。
sbcl --script read_csv.lisp
数値への変換
CSVから読み込んだデータは文字列として扱われるため、数値計算のためには数値に変換する必要があります。
Pythonの to_numeric 関数に相当する機能を実装してみましょう。
```lisp
;; データを数値に変換する関数
(defun to-numeric (data &optional (columns '(1 2)))
"指定された列のデータを数値に変換する(Pythonのto_numericに相当)"
(mapcar (lambda (row)
(loop for i from 0 below (length row)
for cell = (nth i row)
collect (if (member i columns)
(parse-integer cell :junk-allowed t)
cell)))
data))
;; 使用例
(defun demo-numeric-conversion ()
(format t "数値変換テスト:~%")
(multiple-value-bind (header data)
(read-csv-with-header "height_weight.csv")
(let ((numeric-data (to-numeric data)))
(format t "変換後データ:~%")
(dolist (row numeric-data)
(format t " ~A~%" row)))))
```
データフレーム風の実装
Python の pandas の DataFrame に近い機能も実装してみましょう。
```lisp
;; データフレーム風の構造体を定義
(defstruct dataframe
headers
data)
;; データフレームを作成する関数
(defun make-df-from-csv (filename &optional numeric-columns)
"CSVファイルからデータフレームを作成する(Pythonのpd.read_csvに近い機能)"
(multiple-value-bind (headers data) (read-csv-with-header filename)
(let ((processed-data (if numeric-columns
(to-numeric data numeric-columns)
data)))
(make-dataframe :headers headers :data processed-data))))
;; データフレームを表示する関数
(defun display-df (df &optional (n 5))
"データフレームの先頭n行を表示する(Pythonのdf.head()に相当)"
(format t "~%データフレーム:~%")
(format t "ヘッダー: ~A~%" (dataframe-headers df))
(format t "データ(先頭~A行):~%" n)
(loop for row in (subseq (dataframe-data df) 0 (min n (length (dataframe-data df))))
for i from 1 to n
do (format t " ~A~%" row)))
;; 使用例
(defun demo-dataframe ()
(format t "~%データフレーム風の使い方:~%")
(let ((df (make-df-from-csv "height_weight.csv" '(1 2))))
(display-df df)))
```
完全なコード
上記のすべての機能を組み合わせた完全なコード例は以下のようになります。
```lisp
;;; read_csv.lisp - Pythonのpandas.read_csvに相当する機能の実装
;; 文字列をカンマで分割する関数
(defun split-by-comma (line)
(loop for i = 0 then (1+ j)
for j = (position #\, line :start i)
collect (subseq line i j)
until (null j)))
;; CSVファイルを読み込み、リストのリストとして返す関数
(defun read-csv (filename)
"Pythonのpd.read_csvに相当する基本機能"
(with-open-file (stream filename :direction :input)
(loop for line = (read-line stream nil nil)
while line
collect (split-by-comma line))))
;; CSVファイルを読み込み、ヘッダーと本体に分けて返す関数
(defun read-csv-with-header (filename)
"ヘッダー付きCSVファイルを読み込む"
(let* ((all-data (read-csv filename))
(header (first all-data))
(body (rest all-data)))
(values header body)))
;; データを数値に変換する関数
(defun to-numeric (data &optional (columns '(1 2)))
"指定された列のデータを数値に変換する(Pythonのto_numericに相当)"
(mapcar (lambda (row)
(loop for i from 0 below (length row)
for cell = (nth i row)
collect (if (member i columns)
(parse-integer cell :junk-allowed t)
cell)))
data))
;; データフレーム風の構造体を定義
(defstruct dataframe
headers
data)
;; データフレームを作成する関数
(defun make-df-from-csv (filename &optional numeric-columns)
"CSVファイルからデータフレームを作成する(Pythonのpd.read_csvに近い機能)"
(multiple-value-bind (headers data) (read-csv-with-header filename)
(let ((processed-data (if numeric-columns
(to-numeric data numeric-columns)
data)))
(make-dataframe :headers headers :data processed-data))))
;; データフレームを表示する関数
(defun display-df (df &optional (n 5))
"データフレームの先頭n行を表示する(Pythonのdf.head()に相当)"
(format t "~%データフレーム:~%")
(format t "ヘッダー: ~A~%" (dataframe-headers df))
(format t "データ(先頭~A行):~%" n)
(loop for row in (subseq (dataframe-data df) 0 (min n (length (dataframe-data df))))
for i from 1 to n
do (format t " ~A~%" row)))
;; 使用例
(defun demo-read-csv ()
(format t "~%CSVファイル読み込みデモ:~%")
;; 基本的な読み込み
(format t "1. 基本的な読み込み:~%")
(multiple-value-bind (header data)
(read-csv-with-header "height_weight.csv")
(format t "ヘッダー: ~A~%" header)
(format t "データ(生):~%")
(dolist (row data)
(format t " ~A~%" row)))
;; 数値変換
(format t "~%2. 数値変換:~%")
(multiple-value-bind (header data)
(read-csv-with-header "height_weight.csv")
(let ((numeric-data (to-numeric data)))
(format t "データ(数値変換後):~%")
(dolist (row numeric-data)
(format t " ~A~%" row))))
;; データフレーム風の使い方
(format t "~%3. データフレーム風の使い方:~%")
(let ((df (make-df-from-csv "height_weight.csv" '(1 2))))
(display-df df)))
;; メイン関数
(defun main ()
(format t "~%Lispデータ分析 - Pythonライクなread_csv実装~%")
(demo-read-csv))
;; プログラム実行
(main)
```Pythonでは同様の処理が以下のように簡潔に書けます。
import pandas as pd
# CSVファイルを読み込む
df = pd.read_csv('height_weight.csv')
# データの確認
print("データの先頭:")
print(df.head())
# 基本情報
print("\nデータの基本情報:")
print(df.info())Lisp では、Python の pandas のような高機能なライブラリがないため、基本的な機能から自分で実装する必要がありますが、その過程で Lisp の関数型プログラミングの特徴を活かした実装ができました。
特に、Lisp の loop マクロや mapcar 関数などを使うことで、データ処理のコードを簡潔に書くことができます。
おわりに
これで基本的なCSVファイルの読み込み機能が実装できました。
Python では簡単な関数呼び出しで済む処理を、Lispでは一から実装する必要がありますが、Python での経験を活かしながら、Lisp の特徴を理解するよい機会になると思います。
次回は、このデータを使って基本統計量(平均、中央値、分散、標準偏差)を計算する関数を実装していきたいと思います。

コメント