#+File Created: <2021-05-03 Mon 19:03>
#+Last Updated: <2021-09-21 Tue 16:52>

dataframe の取り扱いについて.
以前もほとんど同じことやったけど, version が上がって変わった部分もあったりすっかり忘れてる部分もあったりするのでもう一度改めてやってみよう.


1 環境

計算機環境を書いておこう.
いくつかのパソコンでやったので全部書いておく.
全部 Mac だけど.

環境1
MacBook Air (M1, 2020) メモリ16GB
OS: MacOS Big Sur 11.2.3
python: miniforge3-4.10 (Python3.9.2)
Julia: version 1.6.1 (arm 用バイナリが無い)
R: version 4.0.5 (arm 用バイナリが無い)
ruby: 3.0.1p64
perl: v5.32.1

環境2
iMac(2019) メモリ16GB
OS: MacOS Mojave 10.14.6
python: anaconda3-2020.11 (Python3.8.5)
Julia: version 1.5.3
R: version 4.0.4
ruby: 3.0.1p64
perl: v5.32.1

環境3
MacBook Pro(2019) メモリ32GB
OS: MacOS Catalina 10.15.5
python: anaconda3-2020.11 (Python3.8.5)
Julia: version 1.6.1
R: version 4.0.4
ruby: 3.0.1p64
perl: v5.32.1

2 はじめに

fitbit から心拍数データをとってきた.
こんなデータである.

1: import pandas as pd
2: df = pd.read_csv("data/heart_beats_summary_2021-02.data",sep="\t",header=0, parse_dates=[0])
3: print(df.head(10))
        date           cat         cal   min
0 2021-02-01  Out_of_Range  1548.62703  1298
1 2021-02-01      Fat_Burn   350.27367    93
2 2021-02-01        Cardio   108.05331    15
3 2021-02-01          Peak     0.00000     0
4 2021-02-02  Out_of_Range  1436.70932  1209
5 2021-02-02      Fat_Burn   437.81104    90
6 2021-02-02        Cardio   177.95100    22
7 2021-02-02          Peak     9.49072     1
8 2021-02-03  Out_of_Range  1437.58534  1205
9 2021-02-03      Fat_Burn   762.53009   162

プログラム 2 行目:
pd.read_csv でファイルを読み込む. pd.read_table とほぼ同じ.
区切り文字の default が read_csv のときは ",", read_table のときは "\t"
引数は…

  • ファイル名
  • sep="\t" # 区切り文字
  • header=0 # 列名が書いてある行数(default=0) 列名が無い場合は header=None とする.
  • names=('date','cat','cal','min') # のように自分で列名を指定できる
  • index_col=0 # 行名がある場合. 行名が書いてある列番号を指定できる.
  • usecols=[1,3] # 特定の列だけ読み込む場合(例では 2 列目と 4 列目のみ取り込み)

参考:
pandasでcsv/tsvファイル読み込み(read_csv, read_table) | note.nkmk.me

このデータを, 1 日 1 行にしてグラフ化したい.
こんな感じ:

date out_of_range fat_burn cardio peak
2021-02-01 1298 93 15 0
2021-02-02 1209
         

このデータを例にして, データをいい感じで加工しつつ簡単なグラフを描くところまでをやってみたい.
R, python, Julia, で似たようなことをやるにはどうするかをまとめておく.

3 データ加工

3.1 R

3.2 dataframe

まず普通にデータの読み込み. read.table は read.csv でもよい.

1: df0 <- read.table(file="data/heart_beats_summary_2021-02.data",header=TRUE, sep="\t")
2: print(head(df0,5))
        date          cat       cal  min
1 2021-02-01 Out_of_Range 1548.6270 1298
2 2021-02-01     Fat_Burn  350.2737   93
3 2021-02-01       Cardio  108.0533   15
4 2021-02-01         Peak    0.0000    0
5 2021-02-02 Out_of_Range 1436.7093 1209

特定の列を抽出する. 1,2,4 列目だけ使いたい(3 列目(cal) は今回はいらないので削除したい).
R の配列は 1 から始まる(1-based).

1: df0 <- read.csv(file="data/heart_beats_summary_2021-02.data",header=TRUE, sep="\t")
2: df <- df0[,c(1,2,4)]
3: print(head(df,5))
        date          cat  min
1 2021-02-01 Out_of_Range 1298
2 2021-02-01     Fat_Burn   93
3 2021-02-01       Cardio   15
4 2021-02-01         Peak    0
5 2021-02-02 Out_of_Range 1209

cat 列が "Out_of_Range" の行のみを取り出してみよう.

1: df0 <- read.csv(file="data/heart_beats_summary_2021-02.data",header=TRUE, sep="\t")
2: df <- df0[,c(1,2,4)]
3: dfo <- df[df$cat == "Out_of_Range",]  # 条件を満たす行を抽出
4: print(head(dfo,5))
         date          cat  min
1  2021-02-01 Out_of_Range 1298
5  2021-02-02 Out_of_Range 1209
9  2021-02-03 Out_of_Range 1205
13 2021-02-04 Out_of_Range 1255
17 2021-02-05 Out_of_Range 1289

cat 列は全部同じ値なので要らないか.
df[,c(1,3)] で 1列目と3列目がとれる.

1: df0 <- read.csv(file="data/heart_beats_summary_2021-02.data",header=TRUE, sep="\t")
2: df <- df0[,c(1,2,4)]
3: dfo <- df[df$cat == "Out_of_Range",c(1,3)]
4: print(head(dfo,5))
         date  min
1  2021-02-01 1298
5  2021-02-02 1209
9  2021-02-03 1205
13 2021-02-04 1255
17 2021-02-05 1289

min 列の名前を Out_of_Range に変えたい.

参考:
列名の変更: Rのデータフレームで、列名指定で列名の一部を変更する方法 - Rプログラミングの小ネタ

names(df) で列名がとれるのでこれを使う.

1: df0 <- read.csv(file="data/heart_beats_summary_2021-02.data",header=TRUE, sep="\t")
2: df <- df0[,c(1,2,4)]
3: dfo <- df[df$cat == "Out_of_Range",c(1,3)]
4: names(dfo)[2] <- "Out_of_Range"      # 列名の変更
5: print(head(dfo,5))
         date Out_of_Range
1  2021-02-01         1298
5  2021-02-02         1209
9  2021-02-03         1205
13 2021-02-04         1255
17 2021-02-05         1289

Out_of_Range だけでなく, Fat_Burn, Cardio, Peak の各カテゴリについても同じことをやってみる.
for 文を使う.

参考: 繰り返し文

データフレームの配列を作るには, 以下のようにする.

1: array <- list()   # c() でもよい.
2: array <- append(array, list(add_ele))  # list(add_ele) にしないと "データフレームの配列" にはならない点!!

参考:
Python - Rでデータフレーム の配列を作りたい|teratail
A better way to push and pop to/from lists in R? - Stack Overflow
リスト
Rプログラム (TAKENAKA's Web Page)

 1: df0 <- read.csv(file="data/heart_beats_summary_2021-02.data",header=TRUE, sep="\t")
 2: df <- df0[,c(1,2,4)]
 3: adf <- list()
 4: cats <- c("Out_of_Range","Fat_Burn","Cardio","Peak")
 5: for (cat in cats) {
 6:     dfo <- df[df$cat == cat,c(1,3)]
 7:     names(dfo)[2] <- cat
 8:     adf <- append(adf,list(dfo))
 9: }
10: print(head(adf[[2]],5))   # adf[[1]] 二重にかっこを付ける必要あり
         date Fat_Burn
2  2021-02-01       93
6  2021-02-02       90
10 2021-02-03      162
14 2021-02-04      134
18 2021-02-05      114

ここまでで, 表が何個かできた.
date で merge して一つの表にしたい.
list の最初の要素を取り出すときは以下のように2ステップ必要(めんどくさい)

1: mdf <- adf[  1 ]  # shift(@adf) で最初の要素を取り出す部分
2: adf <- adf[ -1 ]  # shift(@adf) 最初の要素を削除する部分
 1: df0 <- read.csv(file="data/heart_beats_summary_2021-02.data",header=TRUE, sep="\t")
 2: df <- df0[,c(1,2,4)]
 3: adf <- list()
 4: cats <- c("Out_of_Range","Fat_Burn","Cardio","Peak")
 5: for (cat in cats) {
 6:     dfo <- df[df$cat == cat,c(1,3)]
 7:     names(dfo)[2] <- cat
 8:     adf <- append(adf,list(dfo))
 9: }
10: # shift(@adf)
11: mdf <- adf[1]      # Out_of_Range
12: adf <- adf[-1]     # Fat_Burn, Cardio, Peak
13: for (ndf in adf) {
14:     mdf <- merge(mdf, ndf, by="date")    # 表の merge
15: }
16: print(head(mdf,5))
        date Out_of_Range Fat_Burn Cardio Peak
1 2021-02-01         1298       93     15    0
2 2021-02-02         1209       90     22    1
3 2021-02-03         1205      162     14    0
4 2021-02-04         1255      134      5    2
5 2021-02-05         1289      114      6    0

これと同じことは Reduce 関数を使ってもできるようだ.
Reduce 関数とは?
こんな感じで動くものらしい.

Reduce(関数(a,b), 配列) –> x = 関数(配列要素(1), 配列要素(2)) を計算 –> y = 関数(x, 配列要素(3)) –> z = 関数(y, 配列要素(4)) …

参考:
Reduce関数活用例:福利計算シミュレーション
Reduce 関数:
Reduce(関数, リスト)
関数は, 2つの引数をとる.

例:
x <- Reduce(function(a,b) { a+b }, c(1,2,5,7))

プログラムの動きの解説:
まず a に 1, b に 2 を入れる.
a + b を計算. 3 になる.
a に 3, b に 5 を入れる.
a + b を計算. 8 になる.
a に 8, b に 7 を入れる.
a + b を計算. 15 になる.
x に 15 が入る.
はず.

実際にやってみる.

1: data <- c(1,2,5,7)
2: x <- Reduce(function(a,b) { a+b }, data)
3: x
[1] 15

Reduce を使って merge してみる.

 1: df0 <- read.csv(file="data/heart_beats_summary_2021-02.data",header=TRUE, sep="\t")
 2: df <- df0[,c(1,2,4)]
 3: adf <- list()
 4: cats <- c("Out_of_Range","Fat_Burn","Cardio","Peak")
 5: for (cat in cats) {
 6:     dfo <- df[df$cat == cat,c(1,3)]
 7:     names(dfo)[2] <- cat
 8:     #print(head(dfo,5))
 9:     #adf[length(adf)+1] <- dfo
10:     adf <- append(adf,list(dfo))
11: }
12: mdf <- Reduce(function(d1,d2) merge(d1,d2,by="date"), adf)  # <-- ここ
13: print(head(mdf,5))
        date Out_of_Range Fat_Burn Cardio Peak
1 2021-02-01         1298       93     15    0
2 2021-02-02         1209       90     22    1
3 2021-02-03         1205      162     14    0
4 2021-02-04         1255      134      5    2
5 2021-02-05         1289      114      6    0

(omake)
ついでに Reduce 系関数をいくつか調査.

apply:
行ごと, 列ごとに関数を適用して結果を返す.
apply(行列データ, 行ごと(1)or列ごと(2), 関数(ベクトルを引数にとるもの), 関数のその他のオプション)
以下の参考ページが図もあってわかりやすい.
Rプログラム (TAKENAKA's Web Page)

apply 系関数の説明
purrr: ループ処理やapply系関数の決定版 - Heavy Watal

join の種類についてのわかりやすい説明
dplyrを使いこなす!JOIN編 - Qiita
須通り_統計_Tidyverseによるデータフレーム加工(02)dplyr_join による複数データフレームの結合/欠損値補完/ルックアップ
データの結合 - *_join関数

最後は書き出し. write.csv() を使う.

 1: df0 <- read.csv(file="data/heart_beats_summary_2021-02.data",header=TRUE, sep="\t")
 2: df <- df0[,c(1,2,4)]
 3: adf <- list()
 4: cats <- c("Out_of_Range","Fat_Burn","Cardio","Peak")
 5: for (cat in cats) {
 6:     dfo <- df[df$cat == cat,c(1,3)]
 7:     names(dfo)[2] <- cat
 8:     #print(head(dfo,5))
 9:     #adf[length(adf)+1] <- dfo
10:     adf <- append(adf,list(dfo))
11: }
12: mdf <- Reduce(function(d1,d2) merge(d1,d2,by="date"), adf)
13: write.csv(mdf,"data/r.csv", quote=FALSE, row.names=FALSE)

3.3 tidyverse

データの読み込みは read_delim(file, delim, …)
read_tsv(file) でもよい.

参考
readr - 高速で柔軟なテーブル読み込み

1: library(tidyverse)
2: df0 <- read_delim("data/heart_beats_summary_2021-02.data","\t")
3: print(head(df0,5))
# A tibble: 5 x 4
  date       cat            cal   min
  <date>     <chr>        <dbl> <dbl>
1 2021-02-01 Out_of_Range 1549.  1298
2 2021-02-01 Fat_Burn      350.    93
3 2021-02-01 Cardio        108.    15
4 2021-02-01 Peak            0      0
5 2021-02-02 Out_of_Range 1437.  1209

列ごとの型情報も入っているようだ.

特定の列を抽出する

1: library(tidyverse)
2: df0 <- read_tsv("data/heart_beats_summary_2021-02.data")
3: df <- df0[,c(1,2,4)]   # <-- dataframe と同じでいける
4: print(head(df,5))
# A tibble: 5 x 3
  date       cat            min
  <date>     <chr>        <dbl>
1 2021-02-01 Out_of_Range  1298
2 2021-02-01 Fat_Burn        93
3 2021-02-01 Cardio          15
4 2021-02-01 Peak             0
5 2021-02-02 Out_of_Range  1209

cat == "Out_of_Range" の行を取り出す
filter(df, cat=="Out_of_Range") を使う.

1: library(tidyverse)
2: df0 <- read_tsv("data/heart_beats_summary_2021-02.data")
3: df <- df0[,c(1,2,4)]
4: dfo <- filter(df, cat=="Out_of_Range")  # <-- cat 列が "Out_of_Range" のもののみ抽出
5: print(head(dfo,5))
# A tibble: 5 x 3
  date       cat            min
  <date>     <chr>        <dbl>
1 2021-02-01 Out_of_Range  1298
2 2021-02-02 Out_of_Range  1209
3 2021-02-03 Out_of_Range  1205
4 2021-02-04 Out_of_Range  1255
5 2021-02-05 Out_of_Range  1289

cat 列はいらない. 1列目, 3列目のみを取り出したい.
tidyverse では, パイプ %>% を使って以下の様にも書けるようだ.
df %>% select(date,min) # df から date, min 列だけ取り出す場合.

1: library(tidyverse)
2: df0 <- read_tsv("data/heart_beats_summary_2021-02.data")
3: df <- df0[,c(1,2,4)]
4: dfo <- filter(df, cat=="Out_of_Range") %>% select(date,min) # <-- date, min 列だけ取り出す.
5: print(head(dfo,5))
# A tibble: 5 x 2
  date         min
  <date>     <dbl>
1 2021-02-01  1298
2 2021-02-02  1209
3 2021-02-03  1205
4 2021-02-04  1255
5 2021-02-05  1289

dataframe と同じようにも書ける.
普通に 1,3 列目だけ取り出す, という書き方もできる.

1: library(tidyverse)
2: df0 <- read_tsv("data/heart_beats_summary_2021-02.data")
3: df <- df0[,c(1,2,4)]
4: dfo0 <- filter(df, cat=="Out_of_Range")
5: dfo <- dfo0[,c(1,3)]  # <-- 1,3 列目だけ
6: print(head(dfo,5))
# A tibble: 5 x 2
  date         min
  <date>     <dbl>
1 2021-02-01  1298
2 2021-02-02  1209
3 2021-02-03  1205
4 2021-02-04  1255
5 2021-02-05  1289

列名の変更
tidyverse では
new_df <- rename(df, 新しい列名 = 古い列名)

参考:
Rでdplyrをつかって任意の列の列名を変更する方法 - 備忘ログ

1: library(tidyverse)
2: df0 <- read_tsv("data/heart_beats_summary_2021-02.data")
3: df <- df0[,c(1,2,4)]
4: dfo <- filter(df, cat=="Out_of_Range") %>% select(date,min) %>% rename(Out_of_Range = min)
5: print(head(dfo,5))

tidyvers はパイプ %>% で繋げていけるので慣れれば書きやすい. 結構楽しいかも.
df %>% 関数(x,y,z,…) は 関数(df, x,y,z…) と同じ意味.

Out_of_Range だけでなく全部のカテゴリについてやる.
先程と同様, for 文を使おう.

参考:
【R】dplyr rename()の中で変数を使う - Qiita

列名変更の rename だが…
rename の中で変数を使う場合は, rename(!!変数 := 前の列名) とする必要あり. <– 注意
うーん以外とめんどくさい.

 1: library(tidyverse)
 2: df0 <- read_tsv("data/heart_beats_summary_2021-02.data")
 3: df <- df0[,c(1,2,4)]
 4: cats <- c("Out_of_Range", "Fat_Burn", "Cardio", "Peak")
 5: adf <- list()
 6: for (x in cats) {
 7:     #dfo <- filter(df, cat==x) %>% select(date,min) %>% rename(x = min) # これだと x という文字列が入ってしまう.
 8:     dfo <- filter(df, cat==x) %>% select(date,min) %>% rename(!!x := min)
 9:     adf <- append(adf,list(dfo))
10: }
11: print(head(adf[[2]],5))
# A tibble: 5 x 2
  date       Fat_Burn
  <date>        <dbl>
1 2021-02-01       93
2 2021-02-02       90
3 2021-02-03      162
4 2021-02-04      134
5 2021-02-05      114

date で merge して一つの表にする.
ここは dataframe と同じでいける.

 1: library(tidyverse)
 2: df0 <- read_tsv("data/heart_beats_summary_2021-02.data")
 3: df <- df0[,c(1,2,4)]
 4: cats <- c("Out_of_Range", "Fat_Burn", "Cardio", "Peak")
 5: adf <- list()
 6: for (x in cats) {
 7:     dfo <- filter(df, cat==x) %>% select(date,min) %>% rename(!!x := min)
 8:     adf <- append(adf,list(dfo))
 9: }
10: mdf <- adf[[1]]
11: adf <- adf[-1]
12: for (ndf in adf) {
13:     mdf <- full_join(mdf, ndf, by="date")
14: }
15: print(head(mdf,5))
# A tibble: 5 x 5
  date       Out_of_Range Fat_Burn Cardio  Peak
  <date>            <dbl>    <dbl>  <dbl> <dbl>
1 2021-02-01         1298       93     15     0
2 2021-02-02         1209       90     22     1
3 2021-02-03         1205      162     14     0
4 2021-02-04         1255      134      5     2
5 2021-02-05         1289      114      6     0

色々調べてたら, データフレームのリストをまとめて join する方法をみつけた!!

参考:
r - Simultaneously merge multiple data.frames in a list - Stack Overflow

list_of_df %>% reduce(full_join, by="date") こんな感じで書くといけるらしい.

 1: library(tidyverse)
 2: df0 <- read_tsv("data/heart_beats_summary_2021-02.data")
 3: df <- df0[,c(1,2,4)]
 4: cats <- c("Out_of_Range", "Fat_Burn", "Cardio", "Peak")
 5: adf <- list()
 6: for (x in cats) {
 7:     dfo <- filter(df, cat==x) %>% select(date,min) %>% rename(!!x := min)
 8:     adf <- append(adf,list(dfo))
 9: }
10: mdf <- adf %>% reduce(full_join, by="date")   # <-- ここ
11: print(head(mdf,5))
# A tibble: 5 x 5
  date       Out_of_Range Fat_Burn Cardio  Peak
  <date>            <dbl>    <dbl>  <dbl> <dbl>
1 2021-02-01         1298       93     15     0
2 2021-02-02         1209       90     22     1
3 2021-02-03         1205      162     14     0
4 2021-02-04         1255      134      5     2
5 2021-02-05         1289      114      6     0

write_csv を使って csv 形式に書き出す.

 1: library(tidyverse)
 2: df0 <- read_tsv("data/heart_beats_summary_2021-02.data")
 3: df <- df0[,c(1,2,4)]
 4: cats <- c("Out_of_Range", "Fat_Burn", "Cardio", "Peak")
 5: adf <- list()
 6: for (x in cats) {
 7:     dfo <- filter(df, cat==x) %>% select(date,min) %>% rename(!!x := min)
 8:     adf <- append(adf,list(dfo))
 9: }
10: mdf <- adf %>% reduce(full_join, by="date")
11: write_csv(mdf,"data/rv.csv")

3.4 Python

Python では pandas を使ってみる.

3.5 pandas

まずデータを普通に読み込み.
0 列目は日付として読み込み.
2 列目(0-based) は使わないので削除する.

参考:
pandasでcsv/tsvファイル読み込み(read_csv, read_table) | note.nkmk.me

1: import pandas as pd
2: df = pd.read_csv('data/heart_beats_summary_2021-02.data',sep='\t', header=0, parse_dates=[0])
3: df2 = df.iloc[:,[0,1,3]]
4: print(df2.head(5))
        date           cat   min
0 2021-02-01  Out_of_Range  1298
1 2021-02-01      Fat_Burn    93
2 2021-02-01        Cardio    15
3 2021-02-01          Peak     0
4 2021-02-02  Out_of_Range  1209

プログラム2 行目:
read_csv で表敬式データを読み込み.
tab 区切りなら read_table("foo.tsv") で良い.

  • header=0 は 0 行目(最初の行)が column 名の場合.
  • column 名がない場合は header=None とする.
  • read_csv("foo.tsv", names=("A","B","C","D")) –> column 名を A, B, C, D として読み込む.
  • parse_dates= は 0 列目が日付として扱われることを示す.

プログラム 3 行目:
位置の指定方法をここでまとめておく(多分覚えられないけど…).

  • at, loc は, 行名, 列名 を指定して値を得る.
  • iat, iloc は, 行番号,列番号 を指定して値を得る.

取得データについて.

  • at, iat は単独の要素の値を取得する際に使う.
  • loc, iloc は複数の要素の値
    • 複数の要素指定では, リスト [0,1,3] 及びスライス start:stop:step が使える.
    • : あるいは :: で全部という意味になる.

参考:
pandasで任意の位置の値を取得・変更するat, iat, loc, iloc | note.nkmk.me

データ取り出しをやってみる.

1: import pandas as pd
2: df = pd.read_table("data/heart_beats_summary_2021-02.data",header=0, parse_dates=[0])
3: print(df.iat[0,1])    # 0 行 1 列目(0-based)の値
4: #print(df.iloc[::,2])
5: print(df.iloc[: ,2].head(5))  # 全行 2列目(0-based)
Out_of_Range
0    1548.62703
1     350.27367
2     108.05331
3       0.00000
4    1436.70932
Name: cal, dtype: float64

usecols を使うと, 要らない行は最初から読み込まない.
usecols=[0,1,3] で 2 列目(Python の配列番号は 0 から始まる 0-based)を読み込まない.

1: import pandas as pd
2: df = pd.read_csv("data/heart_beats_summary_2021-02.data",sep="\t",header=0, parse_dates=[0], usecols=[0,1,3])
3: print(df.head(5))
        date           cat   min
0 2021-02-01  Out_of_Range  1298
1 2021-02-01      Fat_Burn    93
2 2021-02-01        Cardio    15
3 2021-02-01          Peak     0
4 2021-02-02  Out_of_Range  1209

ここから表をバラバラにして再構成する.
まずは cat 列 == 'Out_of_Range' のデータを取り出す.
df[df['cat'] =='Out_of_Range'] <– こんな感じで取り出せる.

1: import pandas as pd
2: df = pd.read_table("data/heart_beats_summary_2021-02.data",header=0, parse_dates=[0], usecols=[0,1,3])
3: dfo = df[df['cat']=='Out_of_Range']
4: # dfo = df[df.iloc[:,1]=='Out_of_Range']
5: # dfo = df[df.loc[:,'cat']=='Out_of_Range']
6: print(dfo.head(5))
         date           cat   min
0  2021-02-01  Out_of_Range  1298
4  2021-02-02  Out_of_Range  1209
8  2021-02-03  Out_of_Range  1205
12 2021-02-04  Out_of_Range  1255
16 2021-02-05  Out_of_Range  1289

列 cat == 'Out_of_Range' である行を取り出す方法はいくつかあった. これ以外にもあるかもしれん.
df[df['cat'] == 'Out_of_Range'] <– これが上で使ったやつ.
df[df.iloc[:,1] == 'Out_of_Range']
df[df.loc[:,'cat'] == 'Out_of_Range']

cat 列はここでは要らないなぁ. 全部同じ値だし. 消すにはどーすればいいんだろうか.

1: import pandas as pd
2: df = pd.read_table("data/heart_beats_summary_2021-02.data",header=0, parse_dates=[0], usecols=[0,1,3])
3: dfo = df[df['cat']=='Out_of_Range'].iloc[:,[0,2]]
4: #dfo = df[df.iloc[:,1]=='Out_of_Range'].iloc[:,[0,2]]
5: #dfo = df[df.loc[:,'cat']=='Out_of_Range'].loc[:,['date','min']]  # loc でも出来る.
6: print(dfo.head(5))
         date   min
0  2021-02-01  1298
4  2021-02-02  1209
8  2021-02-03  1205
12 2021-02-04  1255
16 2021-02-05  1289

プログラム 3 行目:
df.iloc[:,[0,2]] で df の [0,2] 列目(0-based) のみを取り出す.

min 列の名前を Out_of_Range としたい.

1: import pandas as pd
2: df = pd.read_table("data/heart_beats_summary_2021-02.data",header=0, parse_dates=[0], usecols=[0,1,3])
3: dfo = df[df['cat']=='Out_of_Range'].iloc[:,[0,2]]
4: dfo.columns = ['date','Out_of_Range']  # column 名を指定し直し
5: print(dfo.head(5))
         date  Out_of_Range
0  2021-02-01          1298
4  2021-02-02          1209
8  2021-02-03          1205
12 2021-02-04          1255
16 2021-02-05          1289

全部の cat に対して同じことをやる.

 1: import pandas as pd
 2: df = pd.read_table("data/heart_beats_summary_2021-02.data",header=0, parse_dates=[0], usecols=[0,1,3])
 3: dfo = df[df['cat']=='Out_of_Range'].iloc[:,[0,2]]
 4: dff = df[df['cat']=='Fat_Burn'].iloc[:,[0,2]]
 5: dfc = df[df['cat']=='Cardio'].iloc[:,[0,2]]
 6: dfp = df[df['cat']=='Peak'].iloc[:,[0,2]]
 7: dfo.columns = ['date','Out_of_Range']
 8: dff.columns = ['date','Fat_Burn']
 9: dfc.columns = ['date','Cardio']
10: dfp.columns = ['date','Peak']
11: print(dfo.head(3))
12: print(dff.head(3))
13: print(dfc.head(3))
14: print(dfp.head(3))
        date  Out_of_Range
0 2021-02-01          1298
4 2021-02-02          1209
8 2021-02-03          1205
        date  Fat_Burn
1 2021-02-01        93
5 2021-02-02        90
9 2021-02-03       162
         date  Cardio
2  2021-02-01      15
6  2021-02-02      22
10 2021-02-03      14
         date  Peak
3  2021-02-01     0
7  2021-02-02     1
11 2021-02-03     0

似たような文なので for 文でまとめてしまおう.

1: import pandas as pd
2: df = pd.read_table("data/heart_beats_summary_2021-02.data",header=0, parse_dates=[0], usecols=[0,1,3])
3: adf =[]
4: for cat in ('Out_of_Range','Fat_Burn','Cardio','Peak'):
5:     dfx = df[df['cat'] == cat].iloc[:,[0,2]]
6:     dfx.columns = ['date',cat]
7:     print(dfx.head(3))
8:     adf.append(dfx)
        date  Out_of_Range
0 2021-02-01          1298
4 2021-02-02          1209
8 2021-02-03          1205
        date  Fat_Burn
1 2021-02-01        93
5 2021-02-02        90
9 2021-02-03       162
         date  Cardio
2  2021-02-01      15
6  2021-02-02      22
10 2021-02-03      14
         date  Peak
3  2021-02-01     0
7  2021-02-02     1
11 2021-02-03     0

日付 (date) をキーにして merge して一つの表にする.

1: df3 = df1.merge(df2, on='date')  <-- df1 と df2 を date 列で merge して df3 に格納

参考:
pandas.DataFrameを結合するmerge, join(列・インデックス基準) | note.nkmk.me
配列(リスト)の先頭の要素を削除するには (shift) | hydroculのメモ

 1: import pandas as pd
 2: df = pd.read_table("data/heart_beats_summary_2021-02.data",header=0, parse_dates=[0], usecols=[0,1,3])
 3: adf =[]
 4: for cat in ('Out_of_Range','Fat_Burn','Cardio','Peak'):
 5:     dfx = df[df['cat'] == cat].iloc[:,[0,2]]
 6:     dfx.columns = ['date',cat]
 7:     adf.append(dfx)
 8: mdf = adf.pop(0)   # 先頭の要素を取り出し.
 9: for d in adf:
10:     mdf = mdf.merge(d,on='date')
11: print(mdf.head(5))
        date  Out_of_Range  Fat_Burn  Cardio  Peak
0 2021-02-01          1298        93      15     0
1 2021-02-02          1209        90      22     1
2 2021-02-03          1205       162      14     0
3 2021-02-04          1255       134       5     2
4 2021-02-05          1289       114       6     0

できた.

ファイルに保存しておいてさくっと取り出せるようにしとこう.

 1: import pandas as pd
 2: df = pd.read_table("data/heart_beats_summary_2021-02.data",header=0, parse_dates=[0], usecols=[0,1,3])
 3: adf =[]
 4: for cat in ('Out_of_Range','Fat_Burn','Cardio','Peak'):
 5:     dfx = df[df['cat'] == cat].iloc[:,[0,2]]
 6:     dfx.columns = ['date',cat]
 7:     adf.append(dfx)
 8: mdf = adf.pop(0)   # 先頭の要素を取り出し.
 9: for d in adf:
10:     mdf = mdf.merge(d,on='date')
11: mdf.to_csv("data/h.csv",index=False)  # csv ファイルとして保存

参考:
pandasでcsvファイルの書き出し・追記(to_csv) | note.nkmk.me

tsv(tab 区切り)で保存したい場合は,
df.to_csv("foo.tsv",sep="\t",index=False) などとすればよい.
index=False は行名を書かない.
列名を書かないときは header=False

3.6 Julia

Julia は DataFrames というパッケージのを使うのがいいのか?
以前とは結構変わってるような.

3.7 DataFrams

参考:
Introduction · DataFrames.jl
CSV.jl Documentation · CSV.jl

色々調べて動くプログラムが書いてあるページをやっと見つけた.
Julia でデータフレームを操作する - 裏 RjpWiki

CSV, DataFrames package を予めインストールしておくこと.

1: using CSV, DataFrames
2: df = CSV.read("data/heart_beats_summary_2021-02.data",delim="\t", DataFrame)
3: println(first(df,5))    # head の代わり
4: println(last(df,5))     # tail の代わり
5×4 DataFrame
 Row │ date        cat           cal       min
     │ Date…       String        Float64   Int64
─────┼───────────────────────────────────────────
   1 │ 2021-02-01  Out_of_Range  1548.63    1298
   2 │ 2021-02-01  Fat_Burn       350.274     93
   3 │ 2021-02-01  Cardio         108.053     15
   4 │ 2021-02-01  Peak             0.0        0
   5 │ 2021-02-02  Out_of_Range  1436.71    1209
5×4 DataFrame
 Row │ date        cat           cal        min
     │ Date…       String        Float64    Int64
─────┼────────────────────────────────────────────
   1 │ 2021-02-27  Peak            43.3267      4
   2 │ 2021-02-28  Out_of_Range  1571.49     1374
   3 │ 2021-02-28  Fat_Burn        41.3578     20
   4 │ 2021-02-28  Cardio           0.0         0
   5 │ 2021-02-28  Peak             0.0         0

head -> first
tail -> last のようだ.
つーか何で method 名変えるかなぁ…
前は head, tail で行けた筈なんだけど…

取得列は select=[1,2,4] のようにするようだ.
Julia は R と同じで配列添字は 1 から始まる(1-based).

1: using CSV, DataFrames
2: df = CSV.read("data/heart_beats_summary_2021-02.data",delim="\t", select=[1,2,4], DataFrame)
3: print(first(df,5))    # head の代わり
4: print(last(df,5))     # tail の代わり
5×3 DataFrame
 Row │ date        cat           min
     │ Date…       String        Int64
─────┼─────────────────────────────────
   1 │ 2021-02-01  Out_of_Range   1298
   2 │ 2021-02-01  Fat_Burn         93
   3 │ 2021-02-01  Cardio           15
   4 │ 2021-02-01  Peak              0
   5 │ 2021-02-02  Out_of_Range   12095×3 DataFrame
 Row │ date        cat           min
     │ Date…       String        Int64
─────┼─────────────────────────────────
   1 │ 2021-02-27  Peak              4
   2 │ 2021-02-28  Out_of_Range   1374
   3 │ 2021-02-28  Fat_Burn         20
   4 │ 2021-02-28  Cardio            0
   5 │ 2021-02-28  Peak              0

Read CSV to Data Frame in Julia. Parameters explained. Using CSV.jl… | by Vaclav Dekanovsky | Towards Data Science
によると, CSV ファイルを読んで DataFrame にする方法は 3 つある.
まず最初はこれ.

1: using CSV, DataFrames
2: df = DataFrame(CSV.File("data/heart_beats_summary_2021-02.data",delim="\t",select=[1,2,4]))
3: println(typeof(df))
4: println(first(df,5))
DataFrame
5×3 DataFrame
 Row │ date        cat           min
     │ Date…       String        Int64
─────┼─────────────────────────────────
   1 │ 2021-02-01  Out_of_Range   1298
   2 │ 2021-02-01  Fat_Burn         93
   3 │ 2021-02-01  Cardio           15
   4 │ 2021-02-01  Peak              0
   5 │ 2021-02-02  Out_of_Range   1209

次はこれ. |> はパイプ演算子.

1: using CSV, DataFrames
2: df = CSV.File("data/heart_beats_summary_2021-02.data",delim="\t",select=[1,2,4]) |> DataFrame
3: println(typeof(df))
4: println(first(df,5))
DataFrame
5×3 DataFrame
 Row │ date        cat           min
     │ Date…       String        Int64
─────┼─────────────────────────────────
   1 │ 2021-02-01  Out_of_Range   1298
   2 │ 2021-02-01  Fat_Burn         93
   3 │ 2021-02-01  Cardio           15
   4 │ 2021-02-01  Peak              0
   5 │ 2021-02-02  Out_of_Range   1209

3 つめが上で使ってるやつ.

1: using CSV, DataFrames
2: df = CSV.read("data/heart_beats_summary_2021-02.data", DataFrame; delim="\t",select=[1,2,4])
3: println(typeof(df))
4: println(first(df,5))
DataFrame
5×3 DataFrame
 Row │ date        cat           min
     │ Date…       String        Int64
─────┼─────────────────────────────────
   1 │ 2021-02-01  Out_of_Range   1298
   2 │ 2021-02-01  Fat_Burn         93
   3 │ 2021-02-01  Cardio           15
   4 │ 2021-02-01  Peak              0
   5 │ 2021-02-02  Out_of_Range   1209

データ取り出すとこやってみる.
cat == 'Out_of_Range' のデータ取り出し.
注意点: Julia は ' (シングルクォーテーション) と " (ダブル)を区別する. " (ダブル)じゃないとダメみたいだ.

1: using CSV, DataFrames
2: df = CSV.read("data/heart_beats_summary_2021-02.data",delim="\t", select=[1,2,4], DataFrame)
3: dfo = df[df.cat .== "Out_of_Range", :]
4: print(first(dfo,5))
5×3 DataFrame
 Row │ date        cat           min
     │ Date…       String        Int64
─────┼─────────────────────────────────
   1 │ 2021-02-01  Out_of_Range   1298
   2 │ 2021-02-02  Out_of_Range   1209
   3 │ 2021-02-03  Out_of_Range   1205
   4 │ 2021-02-04  Out_of_Range   1255
   5 │ 2021-02-05  Out_of_Range   1289

df.列名 で列名を持つ列のデータを全て取り出す.
ここでは, プログラム 3 行目の df.cat で cat 列のデータを全て取り出している.
cat 列のデータが "Out_of_Range" である行を全て取り出す.
比較演算子には .(dot) が必要
df.cat .== "Out_of_Range" <– こんな風に書く必要がある.
数値であれば不等号も使えるがそれにも .(ドット)が必要である.
df.min .> 500 とか.
抽出する列が全部であれば : を使う.
cat 列が要らないのであれば : の代わりに ["date","min"] とかにすればいい.
df[df.cat .=="Out_of_Range", :] –> df[df.cat .== "Out_of_Range",["date","min"]] こんな感じ.

index (列番号) でやるにはどうするんだろう.
以下の 3 行目で, 1, 3 列のみ抽出してみる(Julia は R と同様, 配列等は 1-based).

1: using CSV, DataFrames
2: df = CSV.read("data/heart_beats_summary_2021-02.data",delim="\t", select=[1,2,4], DataFrame);
3: dfo = df[df[:,2] .== "Out_of_Range", [1,3]];
4: print(first(dfo,5))
5×2 DataFrame
 Row │ date        min
     │ Date…       Int64
─────┼───────────────────
   1 │ 2021-02-01   1298
   2 │ 2021-02-02   1209
   3 │ 2021-02-03   1205
   4 │ 2021-02-04   1255
   5 │ 2021-02-05   1289

できた.
df[df[:,2] == "Out_of_Range", [1,3]] # df の 2 列目が "Out_of_Range" の 1,3 列目だけとってくる.

列の取得方法のいろいろ:
df.列名
df[!, :列名] :列名 は Symbol としての列名のようだ.
df[!, Symbol("列名")]
df[:, Symbol("列名")]
df[!,列index(1-based)]
df[:,列index(1-based)]

参考:
JuliaでCSV / DataFrameを扱う方法 - Qiita
DataFrames.jl Getting Startedの要点 (2021年3月版)

! と : は何が違うんだろうか.
DataFrames.jl Getting Startedの要点 (2021年3月版)
によると, コピーを作るかどうかのようだ.
代入するときに結果が変わる.

1: df[:, :A][1] = 3   # 代入
2: print(df)   # 変わらない
3: 
4: df[!,:A][1] = 3
5: print(df)   # 変わる

次は列名の変更.

参考:
Change Column Names of a DataFrame - Previous methods don't work - Usage / First steps - JuliaLang

1: using CSV, DataFrames
2: df = CSV.read("data/heart_beats_summary_2021-02.data",delim="\t", select=[1,2,4], DataFrame);
3: dfo = df[df[:,2] .== "Out_of_Range", [1,3]];
4: rename!(dfo, :min => :Out_of_Range)
5: print(first(dfo,5))

プログラム 4 行目で列名の変更を行っている. min -> Out_of_Range に変更.
! がついている関数は破壊的処理をするってどこかに書いてあったような.
ここでは DataFrame dfo の内容が破壊的に変わる(列の名前が変更される).

後々のことを考えて, Out_of_Range の部分を変数にしたいのだが…

1: using CSV, DataFrames
2: cat = "Out_of_Range"
3: df = CSV.read("data/heart_beats_summary_2021-02.data",delim="\t", select=[1,2,4], DataFrame);
4: dfo = df[df[:,2] .== cat, [1,3]];
5: rename!(dfo, :min => cat)   # <-- ここ
6: print(first(dfo,5))
5×2 DataFrame
 Row │ date        Out_of_Range
     │ Date…       Int64
─────┼──────────────────────────
   1 │ 2021-02-01          1298
   2 │ 2021-02-02          1209
   3 │ 2021-02-03          1205
   4 │ 2021-02-04          1255
   5 │ 2021-02-05          1289

普通に rename!(dfo, :min => 変数) でよかった.
じゃこれでもいいってことかな.

1: using CSV, DataFrames
2: cat = "Out_of_Range"
3: df = CSV.read("data/heart_beats_summary_2021-02.data",delim="\t", select=[1,2,4],DataFrame);
4: dfo = df[df[:,2] .== cat, [1,3]];
5: rename!(dfo, "min" => cat)
6: print(first(dfo,5))
5×2 DataFrame
 Row │ date        Out_of_Range
     │ Date…       Int64
─────┼──────────────────────────
   1 │ 2021-02-01          1298
   2 │ 2021-02-02          1209
   3 │ 2021-02-03          1205
   4 │ 2021-02-04          1255
   5 │ 2021-02-05          1289

rename!(dfo, "min" => 変数) で問題なかった.

データの結合

 1: using CSV, DataFrames
 2: df = CSV.read("data/heart_beats_summary_2021-02.data",delim="\t", select=[1,2,4], DataFrame);
 3: adf =[]
 4: for cat in ("Out_of_Range","Fat_Burn","Cardio","Peak")
 5:     dfo = df[df[:,2] .== cat, [1,3]]  # df の 2 列目の値が cat のだけ取り出す. [1,3] 列目だけ取り出す.
 6:     rename!(dfo,"min" => cat)  # dfo の min 列の名前を cat に変更. dfo を変更する.
 7:     push!(adf,dfo)
 8: end
 9: ddf = outerjoin(adf[1], adf[2],  adf[3], adf[4], on = :date);  # 4 つしか無いのでベタ書き
10: println(first(ddf,5))
5×5 DataFrame
 Row │ date        Out_of_Range  Fat_Burn  Cardio  Peak
     │ Date…       Int64?        Int64?    Int64?  Int64?
─────┼────────────────────────────────────────────────────
   1 │ 2021-02-01          1298        93      15       0
   2 │ 2021-02-02          1209        90      22       1
   3 │ 2021-02-03          1205       162      14       0
   4 │ 2021-02-04          1255       134       5       2
   5 │ 2021-02-05          1289       114       6       0

プログラム 3-8 行目
DataFrame の配列 adf を作る. Python では append だが julia では push! (! は破壊的メソッドの意味らしい).

参考:
Julia早引きノート[14]リスト(Array型一次元配列) - Qiita

プログラム 9 行目. date の値で表を merge.
pandas では merge.
julia では join.
innerjoin, leftjoin 等あるけど, ここでは outerjoin を使った.
2つだけじゃなくて何個でも行ける.

参考:
Joins · DataFrames.jl

この DataFrame を CSV.write で保存してとりあえずこの部分は終わりとしよう.

参考:
CSV.jl Documentation · CSV.jl

 1: using CSV, DataFrames
 2: df = CSV.read("data/heart_beats_summary_2021-02.data",delim="\t", select=[1,2,4], DataFrame);
 3: adf =[]
 4: for cat in ("Out_of_Range","Fat_Burn","Cardio","Peak")
 5:     dfo = df[df[:,2] .== cat, [1,3]]  # df の 2 列目の値が cat のだけ取り出す. [1,3] 列目だけ取り出す.
 6:     rename!(dfo,"min" => cat)  # dfo の min 列の名前を cat に変更. dfo を変更する.
 7:     push!(adf,dfo)
 8: end
 9: ddf = outerjoin(adf[1], adf[2],  adf[3], adf[4], on = :date);
10: CSV.write("data/j.csv",ddf, delim=",")

4 グラフ化

4.1 R

4.2 ggplot2(基礎)

ggplot2 によるグラフ作成のテンプレートはこんな感じ.

 1: library(ggplot2)
 2: 
 3: # データをつくる
 4: X <- seq(-4,4, length=100)    # -4 から 4 まで. 100 分割したベクトルを作成
 5: Y <- dnorm(x=X, mean=0, sd=1) # y としてガウス関数
 6: df <- data.frame(xf=X,yf=Y)   # ggplot2 は dataframe を読み込む
 7: 
 8: p <- ggplot(data=df) + geom_line(mapping=aes(x=xf,y=yf))  # x,y は固定
 9: plot(p)
10: fname <- 'images/r0.png'
11: ggsave(p,file=fname,dpi=70)

DataFrame2021-r0.png

時系列データの取り扱い

1: # test.data には日付(date)と時刻(time 09:03:02) 列があるとする.
2: d <- read.table('test.data', sep='\t', header=TRUE)
3: # datetime という新しい列をつくるとき.
4: # datetime 列は "date time" とする
5: d$datetime <- paste(d$date, d$time, sep=' ')
6: # POSIX 形式に変換
7: d$datetime <- as.POSIXct(d$datetime)
8: p <- ggplot(data=d) + geom_lines(mapping=aes(x=datetime, y=Y))

4.3 ggplot2 による時系列グラフの作成

tidyverse を使うと, 日付っぽい部分が勝手に日付オブジェクトとなるので自動でいい感じのグラフが描ける.
Out_of_Range 列のデータのみのグラフを描いてみる

1: library(tidyverse)
2: df <- read_csv("data/rv.csv")  # 作成したもの
3: p <- ggplot(data=df) + geom_line(mapping=aes(x=date,y=Out_of_Range))
4: plot(p)
5: fname = 'images/r01.png'
6: ggsave(p,file=fname,dpi=70)

DataFrame2021-r01.png

プレーンな dataframe を使う場合は, 日付部分を POSIXct クラスに変更する.
3 行目でやってる.

1: library(ggplot2)
2: df <- read.csv(file='data/r.csv',header=TRUE)
3: df$date <- as.POSIXct(df$date)
4: p <- ggplot(data=df) + geom_line(mapping=aes(x=date,y=Out_of_Range))
5: plot(p)
6: fname = 'images/r03.png'
7: ggsave(p,file=fname,dpi=70)

DataFrame2021-r03.png

同じ図が描けた.

一枚のグラフに複数のデータを載せる場合.
Out_of_Range 列と Fat_Burn 列を描く.

1: library(tidyverse)
2: df <- read_csv("data/rv.csv")  # 作成したもの
3: p <- ggplot(data=df) + geom_line(mapping=aes(x=date,y=Out_of_Range), color="blue")
4: p <- p + geom_line(mapping=aes(x=date,y=Fat_Burn),color="orange") + ylab("min")
5: plot(p)
6: fname = 'images/r04.png'
7: ggsave(p,file=fname,dpi=70)

DataFrame2021-r04.png

R の場合は色々処理する前のデータでそのまま描ける.

1: library(tidyverse)
2: df0 <- read_tsv("data/heart_beats_summary_2021-02.data")
3: head(df0)
# A tibble: 6 x 4
  date       cat            cal   min
  <date>     <chr>        <dbl> <dbl>
1 2021-02-01 Out_of_Range 1549.  1298
2 2021-02-01 Fat_Burn      350.    93
3 2021-02-01 Cardio        108.    15
4 2021-02-01 Peak            0      0
5 2021-02-02 Out_of_Range 1437.  1209
6 2021-02-02 Fat_Burn      438.    90
1: library(tidyverse)
2: df0 <- read_tsv("data/heart_beats_summary_2021-02.data")
3: p <- ggplot(data=df0) + geom_line(mapping=aes(x=date,y=min,color=cat))
4: plot(p)
5: fname = 'images/r05.png'
6: ggsave(p,file=fname, dpi=70)

DataFrame2021-r05.png

複数のグラフを描く場合
gridExtra を使おう.

 1: library(tidyverse)
 2: library(gridExtra)
 3: df <- read_csv("data/rv.csv")  # 作成したもの
 4: p1 <- ggplot(data=df) + geom_line(mapping=aes(x=date,y=Out_of_Range), color="blue") + xlab("")
 5: p2 <- ggplot(data=df) + geom_line(mapping=aes(x=date,y=Fat_Burn),     color="orange") + xlab("")
 6: p3 <- ggplot(data=df) + geom_line(mapping=aes(x=date,y=Cardio),       color="magenta") + xlab("")
 7: p4 <- ggplot(data=df) + geom_line(mapping=aes(x=date,y=Peak),         color="cyan")
 8: # grid.arrange(p1,p2,p3,p4,nrow=4)  # 4 つ縦に並べる
 9: h <- arrangeGrob(p1,p2,p3,p4,nrow=4)
10: fname = 'images/r06.png'
11: ggsave(h,file=fname,dpi=70)

DataFrame2021-r06.png

プログラム 4 - 6 行目 xlab("") で x 軸のタイトル "date" を書かない.
プログラム 9 行目 arrangeGrob(p1, p2, p3, p4, nrow=4) nrow=4 で 4 つ縦に並べる.

4.4 Python

4.5 matplotlib(基礎)

matplotlib によるグラフ作成のテンプレートはこんな感じ.
常にオブジェクト指向的に書くのがわかりやすいのでは.
plt class -> figure object を作成 (figure) -> axes object を作成 (add_subplot)-> axes object が一つのグラフを作成

 1: import matplotlib.pyplot as plt
 2: import numpy as np
 3: # データ作成例
 4: x = np.linspace(-4,4, 100)    # -4 から 4 まで 100 分割
 5: # ガウス関数 いちいち定義する必要あるのか... 面倒だなぁ.
 6: def gauss(x,mu,sig):
 7:    return np.exp(-np.power(x -mu, 2.0)/ (2*np.power(sig,2)))
 8: y = gauss(x,0,1)
 9: # データ作成例ここまで
10: # ここからグラフ作成
11: fig = plt.figure()            # plt クラスから figure object
12: ax = fig.add_subplot(1,1,1)   # figure から axes object (1 行 1 列の 1 番目のグラフ = 1 枚のグラフを全体に描く)
13: ax.plot(x,y)                  # axes object が一つのグラフを描画する
14: fig.show()
15: # グラフの保存
16: fname = 'images/p00.png'
17: fig.savefig(fname, format='png', dpi=70)

DataFrame2021-p00.png

時系列グラフでは x 軸が日付・時間であることが多いだろう.
軸目盛の設定(locator, formatter)を軸に対して行う.

参考:
matplotlib.datesで時系列データのグラフの軸目盛の設定をする | 分析小箱
時系列データの可視化:datetime型のx軸操作 - Qiita
Matplotlib で X 軸の目盛りラベルテキストを回転させる方法 | Delft スタック

 1: import matplotlib.dates as mdates
 2: # 目盛の指定
 3: xloc = mdates.HourLocator(byhour=range(0,24,4))  # 0-24時間 まで, 4 時間おきに目盛
 4: # ax は作成された axes object
 5: ax.xaxis.set_major_locator(xloc)                 # minor_locator もある(補助目盛)
 6: # x 軸の文字列 format の指定
 7: xfmt = mdates.DateFormatter("%H:%M") # 目盛の書かせ方. ここでは 00:00 形式で
 8: ax.xaxis.set_major_formatter(xfmt)
 9: # x 軸の文字列を斜めに書くとき
10: labels = ax.get_xticklabels()
11: plt.setp(labels, rotation=45, fontsize=10)

その他の設定事項

 1: import calendar
 2: # ax は作成された axes object
 3: ax.set_title('heart beats summary')
 4: 
 5: ax.set_xlabel('Date')
 6: ax.set_ylabel('heart_beat')
 7: 
 8: dlst = calendar.monthrange(2021,4)[1]  # 月末日
 9: ax.set_xlim(dt.datetime(2021,4,1), dt.datetime(2021,4,dlst))
10: ylim = (50,200)
11: ax.set_ylim(*ylim)

グラフ作成

1: # 積み上げ棒グラフなら
2: ax.bar(df.date, df.Out_of_Range)
3: ax.bar(df.date, df.Fat_Burn, bottom=df.Out_of_Range,color='green')
4: ax.bar(df.date, df.Cardio, bottom=df.Out_of_Range+df.Fat_Burn,color='orange')
5: ax.bar(df.date, df.Peak, bottom=df.Out_of_Range+df.Fat_Burn+df.Cardio,color='red')
1: # 描画
2: fig.show()
3: # 保存
4: fig.save("output.png", format="png", dpi=70)

4.6 matplotlib による時系列グラフの作成

pandas でデータを dataframe に格納し, plt -> fig -> ax object を使って図を描画する.
典型的な一連の流れ.

 1: import pandas as pd
 2: import matplotlib.pyplot as plt
 3: import matplotlib.dates as mdates
 4: 
 5: df = pd.read_csv('data/h.csv',parse_dates=[0])
 6: 
 7: fig = plt.figure(dpi=100, figsize=(4,3))
 8: ax = fig.add_subplot(1,1,1)   # figure から axes object (1 行 1 列の 1 番目)
 9: ax.plot(df.date, df.Out_of_Range)
10: 
11: fname = 'images/p01.png'
12: fig.show()
13: fig.savefig(fname)

DataFrame2021-p01.png

横軸の文字が重なってよくわからん状態になっている. 何とかしないと.
locator, formatter を修正する.
DayLocator, DateFormatter を使う.

参考:
matplotlib.datesで時系列データのグラフの軸目盛の設定をする | 分析小箱
時系列プロットはじめました〜python編〜 - Qiita

 1: import pandas as pd
 2: import matplotlib.pyplot as plt
 3: import matplotlib.dates as mdates
 4: 
 5: df = pd.read_csv('data/h.csv',parse_dates=[0])
 6: 
 7: fig = plt.figure(dpi=100, figsize=(5,3))
 8: ax = fig.add_subplot(1,1,1)   # figure から axes object (1 行 1 列の 1 番目)
 9: ax.plot(df.date, df.Out_of_Range)
10: 
11: # 横軸は時系列
12: xloc = mdates.DayLocator(bymonthday=None, interval=7)
13: xfmt = mdates.DateFormatter("%Y-%m-%d")
14: ax.xaxis.set_major_locator(xloc)
15: ax.xaxis.set_major_formatter(xfmt)
16: # 斜めに書く場合
17: labels = ax.get_xticklabels()
18: plt.setp(labels, rotation=45, fontsize=10)
19: 
20: fname = 'images/p02.png'
21: fig.show()
22: fig.savefig(fname)

DataFrame2021-p02.png

横軸文字列はみ出してるんだけど.
plt.tight_layout() とすると自動で調整してくれるようだ. これは便利.

参考:
matplotlibでグラフの文字サイズを大きくする - Qiita

 1: import pandas as pd
 2: import matplotlib.pyplot as plt
 3: import matplotlib.dates as mdates
 4: 
 5: df = pd.read_csv('data/h.csv',parse_dates=[0])
 6: 
 7: fig = plt.figure(dpi=100, figsize=(5,3))
 8: ax = fig.add_subplot(1,1,1)   # figure から axes object (1 行 1 列の 1 番目)
 9: ax.plot(df.date, df.Out_of_Range)
10: 
11: # 横軸は時系列
12: xloc = mdates.DayLocator(bymonthday=None, interval=7)
13: xfmt = mdates.DateFormatter("%Y-%m-%d")
14: ax.xaxis.set_major_locator(xloc)
15: ax.xaxis.set_major_formatter(xfmt)
16: # 斜めに書く場合
17: labels = ax.get_xticklabels()
18: plt.setp(labels, rotation=45, fontsize=10)
19: # 配置をいい感じにする
20: plt.tight_layout()
21: 
22: fname = 'images/p03.png'
23: fig.show()
24: fig.savefig(fname)

DataFrame2021-p03.png

一つのグラフに複数のグラフを描く場合

参考:
matplotlibでグラフの文字サイズを大きくする - Qiita

 1: import pandas as pd
 2: import matplotlib.pyplot as plt
 3: import matplotlib.dates as mdates
 4: 
 5: df = pd.read_csv('data/h.csv',parse_dates=[0])
 6: 
 7: fig = plt.figure(dpi=100, figsize=(5,3))
 8: ax = fig.add_subplot(1,1,1)   # figure から axes object (1 行 1 列の 1 番目)
 9: ax.plot(df.date, df.Out_of_Range, label='out_of_range')
10: # そのまま書いてけばいい
11: ax.plot(df.date, df.Fat_Burn, label='fat_burn')
12: # 凡例が必要か?
13: ax.legend(fontsize=10)
14: 
15: # 横軸は時系列
16: xloc = mdates.DayLocator(bymonthday=None, interval=7)
17: xfmt = mdates.DateFormatter("%Y-%m-%d")
18: ax.xaxis.set_major_locator(xloc)
19: ax.xaxis.set_major_formatter(xfmt)
20: # 斜めに書く場合
21: labels = ax.get_xticklabels()
22: plt.setp(labels, rotation=45, fontsize=10)
23: # 配置をいい感じにする
24: plt.tight_layout()
25: 
26: fname = 'images/p04.png'
27: fig.show()
28: fig.savefig(fname)

DataFrame2021-p04.png

複数のグラフを揃えて描く場合

 1: import pandas as pd
 2: import matplotlib.pyplot as plt
 3: import matplotlib.dates as mdates
 4: 
 5: df = pd.read_csv('data/h.csv',parse_dates=[0])
 6: 
 7: fig = plt.figure(dpi=100, figsize=(3,5))
 8: ax1 = fig.add_subplot(2,1,1)   # figure から axes object (2 行 1 列の 1 番目)
 9: ax1.plot(df.date, df.Out_of_Range)
10: # 二つ目のグラフ
11: ax2 = fig.add_subplot(2,1,2, sharex=ax1) # 2 行 1 列の 2 番目, ax1 の x 軸を共有する
12: ax2.plot(df.date, df.Fat_Burn, color="green", marker='.') # データに . つきのグラフにしてみる
13: 
14: # 一つ目のグラフの x 軸ラベルは要らない
15: ax1.tick_params(labelbottom=False)
16: 
17: # 横軸は時系列
18: xloc = mdates.DayLocator(bymonthday=None, interval=7)
19: xfmt = mdates.DateFormatter("%Y-%m-%d")
20: ax2.xaxis.set_major_locator(xloc)
21: ax2.xaxis.set_major_formatter(xfmt)
22: # 斜めに書く場合
23: labels = ax2.get_xticklabels()
24: plt.setp(labels, rotation=45, fontsize=10)
25: 
26: # title
27: ax1.set_title('out_of_range',fontsize=10)
28: ax2.set_title('fat_burn', fontsize=10)
29: 
30: # 配置をいい感じにする
31: plt.tight_layout()
32: 
33: fname = 'images/p05.png'
34: fig.show()
35: fig.savefig(fname)

DataFrame2021-p05.png

4.7 plotline による時系列グラフの作成

Python での描画と言えば matplotlib だが,
ggplot2 のように, データ加工前の元々のデータでグラフを描けないんかなぁ. と思ったりもする.
元々のデータ構造はこんな感じなんだけど.

1: import pandas as pd
2: df = pd.read_csv("data/heart_beats_summary_2021-02.data",sep="\t",header=0, parse_dates=[0], usecols=[0,1,3])
3: print(df.head(5))
        date           cat   min
0 2021-02-01  Out_of_Range  1298
1 2021-02-01      Fat_Burn    93
2 2021-02-01        Cardio    15
3 2021-02-01          Peak     0
4 2021-02-02  Out_of_Range  1209

このデータを読み込んで cat ごとに色を変えてグラフを描きたい.

参考:
https://stackoverflow.com/questions/41494942/pandas-dataframe-groupby-plot
https://scentellegher.github.io/programming/2017/07/15/pandas-groupby-multiple-columns-plot.html

上の URL を見てみたが, うーん何かめんどくさそう.
更に調べてたら ggplot2 とほぼ同様の書き方でいける plotline というパッケージがあるのを見つけた.
これを使ったほうがいいかな?

Plotnine :: Anaconda.org

入ってなかったのでインストールする.
こんな風にインストールできるようだ:
shell@: conda install -c conda_forge plotnine

早速これでグラフを作ってみる.

1: import pandas as pd
2: from plotnine import ggplot, geom_line, aes, ggsave
3: import plotnine
4: 
5: df = pd.read_csv("data/heart_beats_summary_2021-02.data",sep="\t",header=0, parse_dates=[0], usecols=[0,1,3])
6: p = ggplot() + geom_line(data=df, mapping=aes(x='date', y='min', color='cat'))
7: fname = 'images/p06.png'
8: ggsave(p,filename=fname, format='png',dpi=70)

DataFrame2021-p06.png

ggplot2 と同じじゃん…
ggplot2 に慣れてるなら結構いいかも?

参考:
Pythonのデータ視覚化パッケージの代替としてのPlotnineの紹介
A Grammar of Graphics for Python — plotnine 0.8.0 documentation

date の label を 45°回転
python - Plotnine rotating labels - Stack Overflow

1: import pandas as pd
2: from plotnine import ggplot, geom_line, aes, ggsave, theme, element_text
3: import plotnine
4: df = pd.read_csv("data/heart_beats_summary_2021-02.data",sep="\t",header=0, parse_dates=[0], usecols=[0,1,3])
5: p = ggplot() + geom_line(data=df, mapping=aes(x='date', y='min', color='cat'))
6: p = p + theme(axis_text_x = element_text(rotation=45, hjust=1))
7: fname = 'images/py07.png'
8: ggsave(p,filename=fname, format='png',dpi=70)

DataFrame2021-py07.png

独立に図を描く
facet_wrap('~cat', nrow=4) # 'cat' ごとに 4 行のグラフを描く.

1: import pandas as pd
2: from plotnine import ggplot, geom_line, aes, ggsave, theme, element_text, facet_wrap
3: import plotnine
4: df = pd.read_csv("data/heart_beats_summary_2021-02.data",sep="\t",header=0, parse_dates=[0], usecols=[0,1,3])
5: p = ggplot(data=df, mapping=aes(x='date',y='min',color='cat')) + geom_line()
6: p = p + theme(axis_text_x = element_text(rotation=45, hjust=1)) + facet_wrap('~cat',nrow=4)
7: fname = 'images/py08.png'
8: ggsave(p,filename=fname, format='png',dpi=70)

DataFrame2021-py08.png

割と素晴らしいかも.

4.8 Julia

4.9 PyPlot(基礎)

PyPlot は, Matplotlib と同様の書き方が出来る. 標準の Plots よりも良いらしい?

参考:
JuliaとMatplotlibでグラフを作る際のハマりポイントとサンプルプログラム集 - EurekaMoments
Julia早引きノート[23]JuliaでのMatplotlibによるグラフ描画 - Qiita –> matplotlib でも使えるよく使うパラメータ

テンプレートはこんな感じか

 1: using PyPlot
 2: 
 3: # ダミーデータ
 4: x = range(0,2π, step=0.1)  # 0 から 2π まで 0.1刻みでベクトルデータを作成
 5: y = []
 6: z = []
 7: # sin と cos にする.
 8: for i in x
 9:     push!(y,sin(i))
10:     push!(z,cos(i))
11: end
12: 
13: # matplotlib と同様に書ける!!
14: fig = figure()
15: ax1 = fig.add_subplot(2,1,1)
16: ax1.plot(x,y)
17: 
18: ax2 = fig.add_subplot(2,1,2)
19: ax2.plot(x,z)
20: 
21: fig.tight_layout()
22: fname = "images/j00.png"
23: fig.savefig(fname)

DataFrame2021-j00.png

PyPlot だとほぼ matplotlib と同じ要領でグラフを描けるようだ.

 1: # 折れ線の場合
 2: plot(x,y, color="m", marker="o", markerfacecolor="r", linestyle="-", label=raw"$\sin(x)$")
 3: # 色: b=blue, g=green, r=red, c=cyan, m=magenta, y=yellow, k=black,w=white
 4: # マーカの種類: o=円, s=square, p=pentagon, *=star, +=プラス記号, D=ダイアモンド
 5: # markerfacecolor=マーカの色
 6: # linestyle=線の種類: -=実戦 --=破線, -.=破線(点入り) :=点線
 7: legend()  # label の文字を凡例として使う.
 8: 
 9: # 棒グラフ
10: bar(x,y)
11: 
12: # ヒストグラム
13: hist(data)
14: 
15: # 散布図
16: scatter(x,y)
17: 
18: title("hoo")
19: xlabel("x label")
20: ylabel("y label")
21: 
22: grid(true)
23: 
24: savefig("hoge.png")
25: 
26: #x 目盛り
27: x_ticks_name([1,2,3], ["first","second","third"]) # 1,2,3 のとこに first, second, third
28: 
29: # 範囲
30: xlim(0, π)
31: ylim(0, nothing)  # 上限が無いばあい. nothing を使う.

4.10 PyPlotによる時系列グラフの作成

Out_of_Range 列の時系列グラフの作成

 1: using CSV, DataFrames, PyPlot
 2: df = CSV.read("data/j.csv",DataFrame)
 3: 
 4: fig = figure(dpi=100, figsize=(4,3))
 5: ax  = fig.add_subplot(1,1,1)
 6: ax.plot(df.date, df.Out_of_Range)
 7: 
 8: fig.tight_layout()
 9: fname="images/j01.png"
10: fig.savefig(fname)

DataFrame2021-j01.png

matplotlib と同じようにいけた…

横軸についても同じように行けるんだろうか.
Python の横軸に関する部分のソースコードは以下だけど.

 1: import matplotlib.pyplot as plt
 2: import matplotlib.dates as mdates
 3: ...
 4: xloc = mdates.DayLocator(bymonthday=None, interval=7)
 5: xfmt = mdates.DateFormatter("%Y-%m-%d")
 6: ax.xaxis.set_major_locator(xloc)
 7: ax.xaxis.set_major_formatter(xfmt)
 8: # 斜めに書く場合
 9: labels = ax.get_xticklabels()
10: plt.setp(labels, rotation=45, fontsize=10)

matplotlib.dates で行けるのかも?
Various Julia plotting examples using PyPlot · GitHub

 1: using CSV, DataFrames, PyPlot
 2: df = CSV.read("data/j.csv",DataFrame)
 3: 
 4: fig = figure(dpi=100, figsize=(4,3))
 5: ax  = fig.add_subplot(1,1,1)
 6: ax.plot(df.date, df.Out_of_Range)
 7: 
 8: xloc = matplotlib.dates.DayLocator(interval=7)  # bymonthly=None <-- None なんて知らんというエラーになってしまったのでここは変更
 9: xfmt = matplotlib.dates.DateFormatter("%Y-%m-%d")
10: ax.xaxis.set_major_locator(xloc)
11: ax.xaxis.set_major_formatter(xfmt)
12: 
13: labels = ax.get_xticklabels()
14: plt.setp(labels, rotation=45, fontsize=10)
15: 
16: fig.tight_layout()
17: fname="images/j02.png"
18: fig.savefig(fname)

DataFrame2021-j02.png

行けたようだ.
同様に, 一つのグラフに複数のグラフを描く場合

 1: using CSV, DataFrames, PyPlot
 2: df = CSV.read("data/j.csv",DataFrame)
 3: 
 4: fig = figure(dpi=100, figsize=(5,3))
 5: ax  = fig.add_subplot(1,1,1)
 6: ax.plot(df.date, df.Out_of_Range, label="out_of_range")
 7: ax.plot(df.date, df.Fat_Burn,     label="fat_burn")
 8: 
 9: ax.legend(fontsize=10)
10: 
11: xloc = matplotlib.dates.DayLocator(interval=7)  # bymonthly=None <-- None なんて知らんというエラー
12: xfmt = matplotlib.dates.DateFormatter("%Y-%m-%d")
13: ax.xaxis.set_major_locator(xloc)
14: ax.xaxis.set_major_formatter(xfmt)
15: 
16: labels = ax.get_xticklabels()
17: plt.setp(labels, rotation=45, fontsize=10)
18: 
19: fig.tight_layout()
20: fname="images/j04.png"
21: fig.savefig(fname)

DataFrame2021-j04.png

複数のグラフを揃えて描く場合

 1: using CSV, DataFrames, PyPlot
 2: df = CSV.read("data/j.csv",DataFrame)
 3: 
 4: fig = figure(dpi=100, figsize=(3,5))
 5: ax1  = fig.add_subplot(2,1,1)
 6: ax1.plot(df.date, df.Out_of_Range, label="out_of_range")
 7: 
 8: ax2  = fig.add_subplot(2,1,2, sharex=ax1)
 9: ax2.plot(df.date, df.Fat_Burn,     label="fat_burn", color="green", marker=".")
10: 
11: # 上のグラフの x 軸ラベルは要らない
12: ax1.tick_params(labelbottom=false)
13: 
14: # ax2 の x 軸ラベル
15: xloc = matplotlib.dates.DayLocator(interval=7)  # bymonthly=None <-- None なんて知らんというエラー
16: xfmt = matplotlib.dates.DateFormatter("%Y-%m-%d")
17: ax2.xaxis.set_major_locator(xloc)
18: ax2.xaxis.set_major_formatter(xfmt)
19: labels = ax2.get_xticklabels()
20: plt.setp(labels, rotation=45, fontsize=10)
21: 
22: # title
23: ax1.set_title("out_of_range", fontsize=10)
24: ax2.set_title("fat_burn",     fontsize=10)
25: 
26: fig.tight_layout()
27: fname="images/j05.png"
28: fig.savefig(fname)

DataFrame2021-j05.png

同じ図ができた.
同じライブラリ使ってるから当然か.

PyPlot のたくさんの例
Various Julia plotting examples using PyPlot · GitHub

4.11 RCall(ggplot2)による時系列グラフの作成

RCall というのを使うと, R のコードが使えるらしい.
Julia 経由で ggplot2 を使ってグラフを描いてみる.

RCall.jl examples · GitHub

 1: using CSV, DataFrames
 2: 
 3: df = CSV.read("data/heart_beats_summary_2021-02.data",delim="\t", DataFrame);
 4: 
 5: using RCall
 6: R"""Sys.setenv(LANG="en")"""
 7: @rimport base as R
 8: @rlibrary ggplot2
 9: 
10: p = ggplot(data=df) + geom_line(mapping=aes(x=:date, y=:min, color=:cat))
11: fname = "images/j06.png"
12: ggsave(p,file=fname,dpi=70)

DataFrame2021-j06.png

 1: using CSV, DataFrames
 2: 
 3: df = CSV.read("data/heart_beats_summary_2021-02.data",delim="\t", DataFrame);
 4: 
 5: using RCall
 6: R"""Sys.setenv(LANG="en")"""
 7: @rimport base as R              # org-babel でここがエラー?
 8: @rlibrary ggplot2               # org-babel でここがエラー?
 9: 
10: p = ggplot(data=df) + geom_line(mapping=aes(x=:date, y=:min, color=:cat))
11: p = p + facet_wrap("~cat",nrow=4)   # rotation=45 は使えないようだ.
12: fname = "images/j07.png"
13: ggsave(p,file=fname,dpi=70)

DataFrame2021-j07.png

Comments