4. 基礎分析#

Summary

  • このページは長すぎるうえ、以降の可視化内容と重複があるため読み飛ばしてOKです

  • 執筆時間は、2023年末から2024年前半、2025年12月から2026年1月にとくに増加した

  • 草稿は合計約80万文字。1行あたり約25字、1文あたり約100文字だった

以下のデータに対して基礎分析を行います:

  • writing_time.csv:本書執筆に関する作業時間を格納したファイル

  • sleeping_time.csv:本書執筆中の睡眠時間を格納したファイル

  • draft_stats.csv:本書の草稿に関する集計情報を格納したファイル

基礎分析とは、データの全体像を把握し、データの特性や傾向を理解するための分析です。 具体的には、欠損値や重複データの確認、各変数の分布の把握、変数間の関係性の探索などを行います。

本ブログでは、本書に倣って以下の方針を採用しています:

  • 基礎分析に「可視化」は含まない

  • 基礎分析は「全体像の把握」と「各列の深堀り」から成る

その根拠等は本書を参照ください。

4.1. 環境構築#

前処理を行うために必要なライブラリをインポートし、変数を定義します。 また、データの入出力先となるディレクトリも作成しておきます。

Hide code cell content
# ファイルパスを扱うためのPathlibモジュールをインポート
from pathlib import Path

# データ処理・分析ライブラリのPandasをインポート
import pandas as pd
Hide code cell content
# 基礎分析の対象となるディレクトリのパス
DIR_IN = Path("../../data/tmp")

# 基礎分析対象となるファイル名
FN_WRITING = "writing_time.csv"
FN_SLEEPING = "sleeping_time.csv"
FN_DRAFT = "draft_stats.csv"

4.2. 執筆時間の基礎分析#

本書の執筆時間を格納した writing_time.csvの基礎分析を行います。 writing_time.csvは以下の列を持つCSVファイルです。

  • date:執筆作業を実施した日付

  • seconds:執筆作業の継続秒数

  • category:執筆作業の属するカテゴリー

  • task:執筆作業のタスク名

4.2.1. 全体像の把握#

全体像を眺めてみましょう。 まずはファイルを読み込み、seconds列からhours列を作成します。

Hide code cell content
# pd.read_csv()関数を用いて、CSVファイルをDataFrameとして読み込む
df_writing = pd.read_csv(DIR_IN / FN_WRITING)

# 新しい列"hours"を作成し、"seconds"列の値を3600で割ることで時間に変換
df_writing["hours"] = df_writing["seconds"] / 3600

headメソッドで先頭5行を見てみましょう。

Hide code cell content
# headメソッドで概要を把握
df_writing.head()
date seconds category task hours
0 2021-12-08 1072 プロトタイピング プロトタイピング 0.297778
1 2021-12-08 203 プロトタイピング プロトタイピング 0.056389
2 2021-12-08 1094 プロトタイピング プロトタイピング 0.303889
3 2021-12-08 862 プロトタイピング プロトタイピング 0.239444
4 2021-12-08 1582 プロトタイピング プロトタイピング 0.439444

hoursを追加することで、執筆作業の継続時間を直感的に理解しやすくなりました。

shapeメソッドでデータフレームの形状を確認します。

Hide code cell content
# shapeメソッドでデータフレームの形状を確認
df_writing.shape
(2476, 5)

合計作業時間を算出してみましょう。

Hide code cell content
# 合計作業時間を算出
total_seconds = df_writing["seconds"].sum()
hours = int(total_seconds // 3600)
minutes = int((total_seconds % 3600) // 60)
secs = int(total_seconds % 60)
print(f"合計作業時間: {hours}時間{minutes}{secs}秒")
合計作業時間: 1161時間50分38秒

本書の執筆には膨大な時間がかかったことがわかります。

describeメソッドで各列の統計情報を算出してみましょう。

Hide code cell content
# describeメソッドで各列の統計情報を算出
df_writing.describe()
seconds hours
count 2476.000000 2476.000000
mean 1689.272213 0.469242
std 1543.742944 0.428817
min 0.000000 0.000000
25% 494.750000 0.137431
50% 1278.000000 0.355000
75% 2489.250000 0.691458
max 11589.000000 3.219167

一回の作業の継続時間の平均や最大値等、興味深い情報が得られました。

念の為、isnaメソッドを用いて欠損率を計算します。

Hide code cell content
# isnaで欠損かいなかを取得し、meanで平均を取る
df_writing.isna().mean()
date        0.0
seconds     0.0
category    0.0
task        0.0
hours       0.0
dtype: float64

全ての列において欠損がないように見えます。

最後に、ユニークな値の数をnuniqueメソッドで確認します。

Hide code cell content
# nuniqueでユニークな値の数を確認
df_writing.nunique()
date         769
seconds     1761
category       5
task          29
hours       1761
dtype: int64

前処理で表現を揃えたため、categorytaskのユニーク数は少ないように見えます。

4.2.2. date列の深堀り#

date(執筆作業の実施日)に関して深堀りしましょう。

date列を日付型に変換し、年、月、日、曜日情報をそれぞれ抽出した列を作成します。

Hide code cell content
# pd.to_datetime()関数を用いて、"date"列を日付型に変換
df_writing["date"] = pd.to_datetime(df_writing["date"])

# 年、月、日、曜日情報をdate列から取得
df_writing["year"] = df_writing["date"].dt.year
df_writing["month"] = df_writing["date"].dt.month
df_writing["day"] = df_writing["date"].dt.day
df_writing["weekday"] = df_writing["date"].dt.weekday

# weekdayと曜日を対応付ける辞書
weekday2yobi = {0: "月", 1: "火", 2: "水", 3: "木", 4: "金", 5: "土", 6: "日"}
# weekdayを元に、weekday2yobiを用いてyobi列に曜日表現を格納
df_writing["yobi"] = df_writing["weekday"].map(weekday2yobi)

対象期間期間を確認しておきましょう。

Hide code cell content
# date列の最小値と最大値を取得
df_writing["date"].min(), df_writing["date"].max()
(Timestamp('2021-12-08 00:00:00'), Timestamp('2026-01-21 00:00:00'))

では、年ごとの傾向はどうだったのでしょうか? groupbyメソッドを用いて以下を集計してみます:

  • 合計作業回数

  • 合計作業時間

  • 1回の作業あたりの平均時間

Hide code cell content
# year列ごとにhours列の行数、合算値、そして平均値を算出
df_writing.groupby("year")["hours"].agg(["count", "sum", "mean"])
count sum mean
year
2021 34 12.063889 0.354820
2022 516 198.702500 0.385082
2023 510 235.905556 0.462560
2024 965 547.573056 0.567433
2025 323 125.680556 0.389104
2026 128 41.918333 0.327487

全ての観点において、2024年まで単調に増加し、2025年以降は減少しているようです。 これは私の実感とも一致しています。

では、月ごとにはどのような結果が得られるでしょうか?

Hide code cell content
# month列ごとにhours列の行数、合算値、そして平均値を算出
df_writing.groupby("month")["hours"].agg(["count", "sum", "mean"])
count sum mean
month
1 412 188.638611 0.457861
2 159 90.788056 0.570994
3 196 107.384722 0.547881
4 209 97.513611 0.466572
5 232 132.631667 0.571688
6 168 71.971944 0.428404
7 155 61.347500 0.395790
8 224 104.600000 0.466964
9 147 54.951111 0.373817
10 77 32.791389 0.425862
11 152 68.324722 0.449505
12 345 150.900556 0.437393

これだけでは解釈しづらいため、ソートしてみます。

Hide code cell content
# month列ごとにhours列の行数、合算値、そして平均値を算出
df_tmp = df_writing.groupby("month")["hours"].agg(["count", "sum", "mean"])
# sum列で降順にソートして表示
df_tmp.sort_values("sum", ascending=False)
count sum mean
month
1 412 188.638611 0.457861
12 345 150.900556 0.437393
5 232 132.631667 0.571688
3 196 107.384722 0.547881
8 224 104.600000 0.466964
4 209 97.513611 0.466572
2 159 90.788056 0.570994
6 168 71.971944 0.428404
11 152 68.324722 0.449505
7 155 61.347500 0.395790
9 147 54.951111 0.373817
10 77 32.791389 0.425862

合計作業時間という観点では、長期休暇を取りやすい月か、本業に比較的余裕のできる3-5月が上位に来ています[1]。 納得感のある結果です。

次は曜日ごとに集計してみましょう。解釈しやすいように、sum列に関して降順に並び替えます。

Hide code cell content
# yobi列ごとにhours列の行数、合算値、そして平均値を算出
df_tmp = df_writing.groupby("yobi")["hours"].agg(["count", "sum", "mean"])
# sum列で降順でソートして表示
df_tmp.sort_values("sum", ascending=False)
count sum mean
yobi
506 270.003611 0.533604
471 227.573889 0.483172
370 178.730000 0.483054
296 133.010000 0.449358
308 131.118333 0.425709
264 117.793611 0.446188
261 103.614444 0.396990

想定通り、土日の合計作業時間が長ようです。 また、平日の序列に関しては、私の繁忙状況やライフスタイルを反映しているように見えます。 例えば金曜は

  • 比較的打合せが少ないため残業が少ない傾向がある

  • 翌日が休みのため、夜に無理をしやすい

という理由から、平日の中でトップに位置していると想像されます。

最後に、year列とmonth列の組み合わせに対して同様の集計を行います。

Hide code cell content
# year列・month列ごとにhours列の行数、合算値、そして平均値を算出
df_writing.groupby(["year", "month"])["hours"].agg(["count", "sum", "mean"])
count sum mean
year month
2021 12 34 12.063889 0.354820
2022 1 114 55.302778 0.485112
2 14 7.565278 0.540377
3 68 20.331111 0.298987
4 67 22.806111 0.340390
5 79 30.977778 0.392124
6 31 12.052500 0.388790
7 29 11.909444 0.410670
8 29 11.271667 0.388678
9 3 0.786111 0.262037
10 7 2.585556 0.369365
11 2 0.445000 0.222500
12 73 22.669167 0.310537
2023 1 47 15.967222 0.339728
2 39 13.197500 0.338397
3 13 7.330833 0.563910
4 4 1.689167 0.422292
5 45 26.821667 0.596037
6 42 18.097222 0.430886
7 42 18.298611 0.435681
8 48 24.848611 0.517679
9 51 21.132778 0.414368
10 20 9.432222 0.471611
11 59 27.949444 0.473719
12 100 51.140278 0.511403
2024 1 115 73.413889 0.638382
2 103 68.862500 0.668568
3 113 79.102222 0.700020
4 138 73.018333 0.529118
5 108 74.832222 0.692891
6 60 28.550833 0.475847
7 84 31.139444 0.370708
8 102 54.281111 0.532168
9 52 23.566667 0.453205
10 47 20.576111 0.437790
11 33 14.963333 0.453434
12 10 5.266389 0.526639
2025 1 8 2.036389 0.254549
2 3 1.162778 0.387593
3 2 0.620556 0.310278
6 35 13.271389 0.379183
8 45 14.198611 0.315525
9 41 9.465556 0.230867
10 3 0.197500 0.065833
11 58 24.966944 0.430465
12 128 59.760833 0.466882
2026 1 128 41.918333 0.327487

2023年末から2024年前半にかけて、集中的に作業時間が増加しています。 2025年末から2026年1月の作業量の増加は、出版直前の追い込み(ゲラ確認、サポートサイト構築)が原因です。

4.2.3. secondshours列の深堀り#

seconds(作業時間[秒])とhours(作業時間[時間])に関して深堀りしてみましょう。

describeメソッドを用いて統計情報を取得します。

Hide code cell content
# seconds列・hours列の統計情報を取得
df_writing[["seconds", "hours"]].describe()
seconds hours
count 2476.000000 2476.000000
mean 1689.272213 0.469242
std 1543.742944 0.428817
min 0.000000 0.000000
25% 494.750000 0.137431
50% 1278.000000 0.355000
75% 2489.250000 0.691458
max 11589.000000 3.219167

3時間以上のレコードが気になります。

というのも、私は作業の切れ目をできる限り正確に記録するようにしています。 つまり、上記のレコードが意味するのは、 3時間以上トイレ休憩すら取らず、作業に集中したケースがある ということです。 私の普段の集中力の無さを鑑みると、ただごとではありません。

Hide code cell content
# hours列を基準に降順ソートし、トップ1のみ表示
df_writing.sort_values("hours", ascending=False, ignore_index=True).head(1)
date seconds category task hours year month day weekday yobi
0 2022-02-13 11589 プロトタイピング プロトタイピング 3.219167 2022 2 13 6

2022年2月13日に連続3時間超の作業が記録されています。 こちらは、本書の前身である 「マンガと学ぶデータビジュアライゼーション」を第2回メディア芸術データベース活用コンテストにて発表した際の記録であると想像されます。

4.2.4. category列の深堀り#

category(作業のカテゴリー)に関して深堀りしてみましょう。

まず、カテゴリー別の作業時間に関して、行数、合算値、そして平均値を算出します。

Hide code cell content
# category列ごとにhours列の行数、合算値、そして平均値を算出
df_writing.groupby("category")["hours"].agg(["count", "sum", "mean"])
count sum mean
category
プロトタイピング 162 74.931944 0.462543
企画立案 56 17.686667 0.315833
原稿執筆 1811 901.728056 0.497917
校正校閲 277 108.289444 0.390937
販促 170 59.207778 0.348281

想定通り、原稿執筆が支配的のようです。

では、作業年との組み合わせて集計すると、どのような結果になるでしょうか?

Hide code cell content
# category列・year列ごとにhours列の行数、合算値、そして平均値を算出
df_writing.groupby(["category", "year"])["hours"].agg(["count", "sum", "mean"])
count sum mean
category year
プロトタイピング 2021 34 12.063889 0.354820
2022 128 62.868056 0.491157
企画立案 2022 56 17.686667 0.315833
原稿執筆 2022 332 118.147778 0.355867
2023 510 235.905556 0.462560
2024 955 542.306667 0.567860
2025 14 5.368056 0.383433
校正校閲 2024 10 5.266389 0.526639
2025 267 103.023056 0.385854
販促 2025 42 17.289444 0.411653
2026 128 41.918333 0.327487

作業フェーズの移り変わりが見られて、非常に面白いですね。

4.2.5. task列の深堀り#

task(執筆作業のタスク名)に関して深堀りしてみましょう。

まず、タスク別の作業時間に関して、行数、合算値、そして平均値を算出します。

Hide code cell content
# task列ごとにhours列の行数、合算値、そして平均値を算出
# 見やすいように、合計作業時間に関して降順ソート
df_writing.groupby("task")["hours"].agg(["count", "sum", "mean"]).sort_values(
    "sum", ascending=False
)
count sum mean
task
上巻3-4章執筆 248 141.817500 0.571845
上巻1-2章執筆 201 114.593056 0.570115
下巻5-6章執筆 194 105.576944 0.544211
データ準備 190 87.096111 0.458401
プロトタイピング 162 74.931944 0.462543
下巻4章執筆 124 73.862222 0.595663
下巻3章執筆 158 70.563611 0.446605
PDF確認 123 58.990278 0.479596
下巻2章執筆 123 58.593333 0.476369
その他執筆 164 51.737500 0.315473
上巻5章執筆 82 48.134722 0.587009
練習問題 121 40.470000 0.334463
上巻6章執筆 76 40.392778 0.531484
下巻1章執筆 86 36.788611 0.427775
上巻7章執筆 79 35.323056 0.447127
文献調査 79 27.503889 0.348150
上巻0章執筆 45 15.011667 0.333593
その他校閲 50 14.901944 0.298039
図の修正 45 12.807222 0.284605
打合せ 9 10.700000 1.188889
著作権対応 28 10.154444 0.362659
サポートサイト公開 29 7.871667 0.271437
サポートサイト修正 11 7.157222 0.650657
その他販促 10 6.035000 0.603500
扉図作成 20 4.278333 0.213917
SNS 9 3.914444 0.434938
下巻Appendix執筆 4 1.703056 0.425764
下巻0章執筆 3 0.524167 0.174722
その他準備 3 0.409167 0.136389

合計作業時間という観点では、上巻3-4章が最も長いようです。 上巻3-4章は、マンガデータを用いたハンズオンを扱う章です。

念の為、taskごとに紐づけられているcategoryのユニーク数を集計しておきましょう。

Hide code cell content
# task列を基準に、対応付けられているcategory列のユニーク数を集計
df_writing.groupby("task")["category"].nunique().reset_index()
task category
0 PDF確認 1
1 SNS 1
2 その他執筆 1
3 その他校閲 1
4 その他準備 1
5 その他販促 1
6 サポートサイト修正 1
7 サポートサイト公開 1
8 データ準備 1
9 プロトタイピング 1
10 上巻0章執筆 1
11 上巻1-2章執筆 1
12 上巻3-4章執筆 1
13 上巻5章執筆 1
14 上巻6章執筆 1
15 上巻7章執筆 1
16 下巻0章執筆 1
17 下巻1章執筆 1
18 下巻2章執筆 1
19 下巻3章執筆 1
20 下巻4章執筆 1
21 下巻5-6章執筆 1
22 下巻Appendix執筆 1
23 図の修正 1
24 扉図作成 1
25 打合せ 3
26 文献調査 2
27 練習問題 1
28 著作権対応 1

「打合せ」と「文献調査」に関しては複数のcategoryに対応付けられているようです。

Hide code cell content
# タスク名が「打合せ」あるいは「分顕調査」に該当するレコードを抽出
# category列・task列でhours列の合計を集計して表示
df_writing[df_writing["task"].isin(["打合せ", "文献調査"])].groupby(["task", "category"])[
    "hours"
].sum().reset_index()
task category hours
0 打合せ 企画立案 1.759167
1 打合せ 原稿執筆 8.024167
2 打合せ 販促 0.916667
3 文献調査 企画立案 15.518333
4 文献調査 原稿執筆 11.985556

「打合せ」は「企画立案」「原稿執筆」「販促」フェーズにおいて、「文献調査」は「企画立案」「原稿執筆」フェーズにおいて発生したようです。

4.3. 睡眠時間の基礎分析#

本書執筆期間中の睡眠時間を格納した sleeping_time.csvの基礎分析を行います。 sleeping_time.csvは以下の列を持つCSVファイルです:

  • date:日付

  • category:カテゴリー名(睡眠のみ)

  • task:タスク名(睡眠のみ)

  • seconds:継続秒数

writing_time.csvと異なり、一日ごとに合計睡眠時間が記録されている点に注意しましょう。

4.3.1. 全体像の把握#

全体像を眺めてみましょう。 まずはファイルを読み込み、seconds列からhours列を作成します。

Hide code cell content
# pd.read_csv()関数を用いて、CSVファイルをDataFrameとして読み込む
df_sleeping = pd.read_csv(DIR_IN / FN_SLEEPING)

# 新しい列"hours"を作成し、"seconds"列の値を3600で割ることで時間に変換
df_sleeping["hours"] = df_sleeping["seconds"] / 3600

headメソッドで先頭5行を見てみましょう。

Hide code cell content
# headメソッドで先頭5行を確認
df_sleeping.head()
date category task seconds hours
0 2019-02-23 睡眠 睡眠 24241.0 6.733611
1 2019-02-24 睡眠 睡眠 21131.0 5.869722
2 2019-02-25 睡眠 睡眠 17259.0 4.794167
3 2019-02-26 睡眠 睡眠 21990.0 6.108333
4 2019-02-27 睡眠 睡眠 22461.0 6.239167

2019年2月23日から睡眠時間が記録されているようです。 次はshapeメソッドで形状を確認してみましょう。

Hide code cell content
# shapeメソッドでデータフレームの形状を確認
df_sleeping.shape
(2526, 5)

describeメソッドで各列の統計情報を算出します。

Hide code cell content
# describeメソッドで各列の統計情報を算出
df_sleeping.describe()
seconds hours
count 2526.000000 2526.000000
mean 23775.055028 6.604182
std 3594.488024 0.998469
min 0.000000 0.000000
25% 22118.250000 6.143958
50% 23568.500000 6.546806
75% 25269.750000 7.019375
max 52474.000000 14.576111

一日の睡眠時間の平均や最小・最大値に関する情報が得られました。

isnaメソッドを用いて欠損率を計算します。

Hide code cell content
# isnaで欠損かいなかを取得し、meanで平均を取る
df_sleeping.isna().mean()
date        0.0
category    0.0
task        0.0
seconds     0.0
hours       0.0
dtype: float64

全ての列において欠損がないように見えます。

では、睡眠時間が記録されなかった日は存在するのでしょうか?

Hide code cell content
# 睡眠時間が記録されていない日付を抽出
df_sleeping[df_sleeping["seconds"]==0]
date category task seconds hours
114 2019-06-17 睡眠 睡眠 0.0 0.0
203 2019-09-14 睡眠 睡眠 0.0 0.0
210 2019-09-21 睡眠 睡眠 0.0 0.0
514 2020-07-21 睡眠 睡眠 0.0 0.0
1364 2022-11-18 睡眠 睡眠 0.0 0.0
1446 2023-02-08 睡眠 睡眠 0.0 0.0

詳細は覚えておりませんが、とても忙しかったのかもしれません…。

最後に、ユニークな値の数を nunique メソッドで確認します。

Hide code cell content
# nuniqueメソッドで各列のユニークな値の数を集計
df_sleeping.nunique()
date        2526
category       1
task           1
seconds     2195
hours       2195
dtype: int64

想定通り、categoryおよびtaskは一つ(睡眠)のみであることが確認できました。

4.3.2. date列の深堀り#

date(睡眠時間を記録した日付)に関して深堀りしてみましょう。

date列を日付型に変換し、年、月、日、曜日情報をそれぞれ抽出した列を作成します。

Hide code cell content
# pd.to_datetime()関数を用いて、"date"列を日付型に変換
df_sleeping["date"] = pd.to_datetime(df_sleeping["date"])

# 年、月、日、曜日情報をdate列から取得
df_sleeping["year"] = df_sleeping["date"].dt.year
df_sleeping["month"] = df_sleeping["date"].dt.month
df_sleeping["day"] = df_sleeping["date"].dt.day
df_sleeping["weekday"] = df_sleeping["date"].dt.weekday

# weekdayと曜日を対応付ける辞書
weekday2yobi = {0: "月", 1: "火", 2: "水", 3: "木", 4: "金", 5: "土", 6: "日"}
# weekdayを元に、weekday2yobiを用いてyobi列に曜日表現を格納
df_sleeping["yobi"] = df_sleeping["weekday"].map(weekday2yobi)

対象期間を確認しておきましょう。

Hide code cell content
# date列の最小値と最大値を取得
df_sleeping["date"].min(), df_sleeping["date"].max()
(Timestamp('2019-02-23 00:00:00'), Timestamp('2026-01-22 00:00:00'))

writing_time.csvより広い期間を対象としている点に注意が必要です。

では、年ごとの傾向はどうだったのでしょうか? groupbyメソッドを用いて以下を集計してみます。

  • 日数

  • 合計睡眠時間

  • 1日あたりの平均睡眠時間

Hide code cell content
# year列ごとにhours列のタスク数、合計時間数、そしてタスク1つあたりの平均時間を取得
df_sleeping.groupby("year")["hours"].agg(["count", "sum", "mean"])
count sum mean
year
2019 312 1950.480278 6.251539
2020 366 2414.506944 6.597014
2021 365 2467.735556 6.760919
2022 365 2488.688889 6.818326
2023 365 2508.851667 6.873566
2024 366 2374.918056 6.488847
2025 365 2334.192778 6.395049
2026 22 142.789444 6.490429

count列から、開始・終了年を除き、毎日1レコードを記録していることがわかります。2020年と2024年は閏年であることに注意しましょう。

では、月ごとの傾向はどうでしょうか? meanで昇順ソートしたものを見てみましょう。

Hide code cell content
# month列ごとにhours列の行数、合算値、そして平均値を算出
# 解釈しやすいようにmeanを基準に昇順ソート
df_sleeping.groupby("month")["hours"].agg(["count", "sum", "mean"]).sort_values("mean")
count sum mean
month
4 210 1345.069722 6.405094
2 176 1129.661667 6.418532
3 217 1403.541667 6.467934
5 217 1425.471389 6.568993
6 210 1381.043889 6.576399
12 217 1427.598611 6.578795
7 217 1434.464167 6.610434
9 210 1389.378889 6.616090
11 210 1397.222222 6.653439
1 208 1386.513611 6.665931
10 217 1474.901667 6.796782
8 217 1487.296111 6.853899

4、2、3月の睡眠時間が短い傾向があるのは、本業の繁忙期が原因かもしれません。

次は曜日ごとの傾向を見てみましょう。こちらも、解釈しやすいようにmeanで昇順ソートして表示します。

Hide code cell content
# yobi列ごとにhours列の行数、合算値、そして平均値を算出
# 解釈しやすいようにmeanを基準に昇順ソート
df_sleeping.groupby("yobi")["hours"].agg(["count", "sum", "mean"]).sort_values("mean")
count sum mean
yobi
361 2359.339722 6.535567
361 2362.276667 6.543703
361 2372.609444 6.572325
360 2367.307222 6.575853
361 2380.726111 6.594809
361 2418.700833 6.700002
361 2421.203611 6.706935

曜日に関してはあまり納得感がありませんが、こういうものなのでしょう。

4.4. 草稿の基礎分析#

本書の草稿の集計情報を格納したdraft_stats.csvの基礎分析を行います。 draft_stats.csvは以下の列を持つCSVファイルです:

  • vol:上巻あるいは下巻。前者はvol1、後者はvol2

  • sec:章名。基本的にはf"{小番号:02}"で表現されるが、00は前付け、appendixは付録を表す。

  • chars:合計文字数

  • lines:合計行数

  • commas:合計読点数

  • periods:合計句点数

  • size:ファイルサイズ

  • fns:合計脚注数

  • images:合計画像数

  • codes:合計コードブロック数

  • bolds:合計太字数

4.4.1. 全体像の把握#

全体像を眺めてみましょう。 まずはファイルを読み込みます。

Hide code cell content
# pd.read_csv()関数を用いて、CSVファイルをDataFrameとして読み込む
df_draft = pd.read_csv(DIR_IN / FN_DRAFT)

headメソッドで先頭5行を確認します。

Hide code cell content
# headメソッドで先頭5行を確認
df_draft.head()
vol sec chars lines commas periods size fns images codes bolds
0 vol1 00 13754 562 248 184 31233 6 7 13 5
1 vol1 01 28799 1005 703 396 70533 41 60 0 72
2 vol1 02 35034 1340 893 540 86916 25 110 1 105
3 vol1 03 46086 1872 629 396 91200 18 58 51 43
4 vol1 04 78336 3033 1260 751 173095 27 159 75 126

shapeメソッドでデータフレームの形状を確認します。

Hide code cell content
# shapeメソッドでデータフレームの形状を確認
df_draft.shape
(16, 11)

describeメソッドで各列の統計情報を算出します。

Hide code cell content
# describeメソッドで各列の統計情報を算出
df_draft.describe()
chars lines commas periods size fns images codes bolds
count 16.000000 16.000000 16.000000 16.000000 16.000000 16.000000 16.000000 16.000000 16.000000
mean 49857.750000 1966.500000 767.500000 493.000000 106373.250000 19.000000 88.937500 58.312500 58.750000
std 26540.643711 1077.803136 385.691587 233.523161 55583.685253 11.248704 53.558029 56.620042 36.837481
min 2207.000000 119.000000 29.000000 21.000000 4509.000000 2.000000 0.000000 0.000000 3.000000
25% 33475.250000 1256.250000 649.250000 396.000000 82820.250000 13.000000 59.500000 13.000000 38.250000
50% 52655.000000 2010.500000 720.500000 509.000000 100972.000000 18.000000 94.500000 58.000000 62.000000
75% 75422.000000 2872.500000 1006.000000 668.250000 154485.250000 25.500000 133.250000 79.000000 81.500000
max 85244.000000 3611.000000 1312.000000 854.000000 186358.000000 41.000000 159.000000 225.000000 126.000000

isnaメソッドで、欠損率を算出します。

Hide code cell content
# isnaで欠損かいなかを取得し、meanで平均を取る
df_draft.isna().mean()
vol        0.0
sec        0.0
chars      0.0
lines      0.0
commas     0.0
periods    0.0
size       0.0
fns        0.0
images     0.0
codes      0.0
bolds      0.0
dtype: float64

全ての列において欠損がないように見えます。

最後に、nuniqueメソッドでユニークな値の数を集計します。

Hide code cell content
# nuniqueメソッドでユニークな値の数を集計
df_draft.nunique()
vol         2
sec         9
chars      16
lines      16
commas     16
periods    15
size       16
fns        12
images     15
codes      15
bolds      15
dtype: int64

volについては想定通り2種類(vol1vol2)のみ存在することが確認できました。

4.4.2. vol列の深堀り#

vol(巻)に関して深堀りしてみましょう。 まずは、各巻に含まれる章数を確認します。

Hide code cell content
# volごとにsecのユニークな値を列挙
df_draft.groupby("vol")["sec"].unique()
vol
vol1          [00, 01, 02, 03, 04, 05, 06, 07]
vol2    [00, 01, 02, 03, 04, 05, 06, appendix]
Name: sec, dtype: object

vol1(上巻)は00から07まで、vol200から06までに加えてappendixが含まれているようです。 繰り返しになりますが、00は前付けを表します。

では、巻ごとの各種集計結果を見てみましょう。

Hide code cell content
# 巻ごとに各数値の集計結果を表示
df_draft.groupby("vol")[
    ["chars", "lines", "commas", "periods", "size", "fns", "images", "codes", "bolds"]
].sum()
chars lines commas periods size fns images codes bolds
vol
vol1 379211 15356 6091 3933 804007 185 676 482 502
vol2 418513 16108 6189 3955 897965 119 747 451 438

まず、charslinessizeだけを見ると、vol1よりvol2がボリュームが大きいことがわかります。 一方でfnscodes、そしてboldsに関してはvol1のほうが大きいことが印象的です。

4.4.3. sec列の深堀り#

sec(章)に関して深堀りしてみましょう。まず、ユニークな値を列挙してみます。

Hide code cell content
# sec列のユニークな値を列挙
df_draft["sec"].unique()
array(['00', '01', '02', '03', '04', '05', '06', '07', 'appendix'],
      dtype=object)

各章の登場回数を数え上げます。

Hide code cell content
# sec列中の要素の登場回数を数え上げる
df_draft["sec"].value_counts()
sec
00          2
01          2
02          2
03          2
04          2
05          2
06          2
07          1
appendix    1
Name: count, dtype: int64

前述した通り、07に関してはvol1(上巻)のみに、appendixについてはvol2(下巻)のみに存在します。

4.4.4. chars列の深堀り#

chars(文字数)について深堀りしてみましょう。まず前提のおさらいですが、本稿における「文字数」とは、

chars = len(content)

のようにlenメソッドによって計算される値を指します。 詳細は草稿の統計情報の取得を参照ください。

まずは、合計文字数を確認してみましょう。

Hide code cell content
# chars列の合計を算出
df_draft["chars"].sum()
797724

一般的に、4年制大学の卒業論文は2万〜4万字を目安とすることがあるようです。 単純比較することに意味はありませんが、その20-40倍程度の規模であることがわかりました。

では、文字数が特に多いのはどの章でしょうか?

Hide code cell content
# chars列を基準に降順ソートし、トップ5を表示
df_draft.sort_values("chars", ascending=False).head()
vol sec chars lines commas periods size fns images codes bolds
12 vol2 04 85244 3276 1312 854 186358 38 158 91 86
5 vol1 05 79409 3611 925 638 151478 27 106 225 56
4 vol1 04 78336 3033 1260 751 173095 27 159 75 126
11 vol2 03 76151 3121 1138 724 163507 18 152 93 80
14 vol2 06 75179 2819 1238 720 167637 10 131 65 98

草稿時点で最も文字数が多いのは、vol2(下巻)の04(4章)のようです。 この章では、変数間の関係を見るための7種類の手法を網羅的に紹介しました。 下巻1-4章は同様の構成を採用していますが、紹介する手法数が単調に増加しています。

  • 下巻1章:4手法

  • 下巻2章:5手法

  • 下巻3章:6手法

  • 下巻4章:7手法

文字数も同様に単調増加しているかどうか確認してみましょう。

Hide code cell content
# 下巻の1-4章を抽出して表示
df_draft[(df_draft["vol"] == "vol2") & df_draft["sec"].isin(["01", "02", "03", "04"])]
vol sec chars lines commas periods size fns images codes bolds
9 vol2 01 39984 1656 656 414 88378 14 100 45 47
10 vol2 02 63862 2423 962 651 140348 20 140 73 68
11 vol2 03 76151 3121 1138 724 163507 18 152 93 80
12 vol2 04 85244 3276 1312 854 186358 38 158 91 86

想定通り、文字数に関しても下巻1章 < 下巻2章 < 下巻3章 < 下巻4章となっているようです。

4.4.5. lines列の深堀り#

lines(行数)に関して深堀りしてみましょう。まず前提のおさらいですが、本稿における「行数」とは、

lines = content.count("\n") + 1

のように"\n"の数 + 1 [2] によって算出されています。 詳細は草稿の統計情報の取得を参照ください。

合計行数を確認してみます。

Hide code cell content
# lines列の合計値を算出
df_draft["lines"].sum()
31464

1行あたりの文字数も気になります。

Hide code cell content
# chars列の合計値をlines列の合計値で割る
df_draft["chars"].sum() / df_draft["lines"].sum()
25.35354691075515

おおよそ1行あたり25文字程度のようです。ただし、以下に留意する必要があります:

  • 差分管理のしやすさを優先し、文章の途中でも意図的に改行を入れている

  • コードブロック、脚注、太字等の特殊記法の前後に意図的に改行を入れている

例えば、以下は上巻1.1節の一部です:

## データ可視化はとても身近

**データ可視化**
とは何でしょうか?
データ可視化とは、簡単に言うと「複雑なデータを人間にとって理解しやすい形に変換する方法」です。
他の文献ではデータビジュアライゼーション、あるいはデータ視覚化と呼ばれることもあります。
理解しやすい形に変換する必要があるわけですから、
データ可視化対象となるのは、そのままでは「一見」して全貌を把握できない規模や形式の数値データ、テキストデータ、そして地理データなどがあるでしょう。

**データ可視化**のように、太字で強調する文字列は必ず改行を挟んでいます。 また、理解しやすい形に変換する必要があるわけですから、等、文章の途中でも構わずに改行を入れています。

では、行数が多いのはどの章でしょうか?

Hide code cell content
# lines列を基準に降順ソートし、トップ5を表示
df_draft.sort_values("lines", ascending=False).head()
vol sec chars lines commas periods size fns images codes bolds
5 vol1 05 79409 3611 925 638 151478 27 106 225 56
12 vol2 04 85244 3276 1312 854 186358 38 158 91 86
11 vol2 03 76151 3121 1138 724 163507 18 152 93 80
4 vol1 04 78336 3033 1260 751 173095 27 159 75 126
14 vol2 06 75179 2819 1238 720 167637 10 131 65 98

文字数と異なり、vol1(上巻)の05(5章)の行数が最も多いようです。 上巻5章では、Python、Pandas、Plotlyの基礎を取り扱います。 codes列を見ていただくと分かる通り、例として取り上げるコードが圧倒的に多いことが特徴です。 (草稿としての)可読性を高めるためコードブロックの前後に改行を入れていますし、 コードブロック内部でも積極的にコメント文を入れています[3]。 上巻5章の草稿の行数が膨らんだ理由の一つは、そこにあるかもしれません。

では、行数の絶対量ではなく、文字数に対して相対的に行数が多い章はどれでしょうか?

Hide code cell content
# lines列をchras列で割った結果を格納するlines/chars列を追加し、それに基づいて降順ソート
# headメソッドを用いてトップ5を表示
df_draft.assign(**{"lines/chars": lambda x: x["lines"] / x["chars"]}).sort_values(
    "lines/chars", ascending=False
).head()
vol sec chars lines commas periods size fns images codes bolds lines/chars
15 vol2 appendix 2207 119 29 21 4509 2 0 4 5 0.053919
5 vol1 05 79409 3611 925 638 151478 27 106 225 56 0.045473
6 vol1 06 59224 2574 725 478 110744 18 89 100 24 0.043462
8 vol2 00 12914 545 138 153 25791 2 6 13 3 0.042202
9 vol2 01 39984 1656 656 414 88378 14 100 45 47 0.041417

予想に反して、vol2(下巻)のappendix(付録)がトップとなりました。 文字数が比較的少ないにもかかわらず、コードブロック・脚注・太字等の改行の機会がそれなりにあったことが原因と考えられます。

逆に、文字数に対して行数が少ない章はどのような顔ぶれになるのでしょうか?

Hide code cell content
# lines列をchars列で割った結果を格納するlines/chars列を追加し、それに基づいて昇順ソート
# headメソッドを用いてトップ5を表示
df_draft.assign(**{"lines/chars": lambda x: x["lines"] / x["chars"]}).sort_values(
    "lines/chars"
).head()
vol sec chars lines commas periods size fns images codes bolds lines/chars
13 vol2 05 62972 2149 716 418 121437 15 60 67 51 0.034126
1 vol1 01 28799 1005 703 396 70533 41 60 0 72 0.034897
7 vol1 07 38569 1359 708 550 88808 23 87 17 71 0.035236
14 vol2 06 75179 2819 1238 720 167637 10 131 65 98 0.037497
10 vol2 02 63862 2423 962 651 140348 20 140 73 68 0.037941

vol2(下巻)の05(5章)がトップのようです。 下巻の5章では、メディア展開データのハンズオンの前半として、データの取得・前処理・基礎分析を取り扱います。 草稿を眺めてみると、他の章と比較して箇条書きが多用されていることに気づきます。

例えば以下は、マンガ作品とアニメ作品を紐づけるac_cc.csvを自作する際の判断基準を記したコラムの抜粋です。 ここに限らず、マンガ作品名やアニメ作品名を箇条書き[4]内で列挙する場面がいくつか登場します。

<column>
##Column `ac_cc.csv`の作成基準の具体例

例を用いて、`ac_cc.csv`の作成基準を具体的に示します。

- 対象アニメ作品:
    - テレビレギュラー番組のみを対象とし、OVA(Original Video Animation)は対象としない
    - アニメオリジナルの続編・スピンオフ等は、たとえ原作と共通の世界観を持っていたとしても除外する
        - 例:アニメ`タッチ`シリーズ(`タッチ Miss Lonely Yesterday あれから、君は・・・`、`タッチ CROSS ROAD 風のゆくえ`)
        - 例:アニメ`遊戯王`シリーズ(`遊戯王 VRAINS`、`遊☆戯☆王 デュエルモンスターズ GX`、`遊☆戯☆王5D'S`、`遊★戯★王 ZEXAL`、`遊★戯★王アーク・ファイブ ARC-V`)
        - 例:アニメ`マジンガーZ`シリーズ(`グレートマジンガー`、`マジンガーzip`)
        - 例:アニメ`DRAGON BALL`シリーズ(`DRAGON BALL GT`、`DRAGON BALL 超[スーパー]`)

…(略)…

また、特殊なケースであるため影響は限定的ですが、非常に長いアニメ各話名を扱っているのもこの章です。 例えば以下は、5.3節の基礎分析の一部です。

四大少年誌に掲載されたマンガ作品を原作としたアニメ作品のうち、各話名が最も長かったのは、`School Rumble`の`[突然の「さよなら」…迷い 込んだラビリンス…あなたはだれ? …教えて。「すれちがい」「片想い」 とどけ、ボクの気持ち。とどけ、ワ タシの想い。たぶん一度しかない季 節、青春の1ページ。これが最後の チャンス、確かめたい…キミの気持 ち。伝わる言葉、伝わらない想い。 あの日の告白、永遠の一日、だけど …いつまでも続いていく、わたした ちの「いま」。そして明日へ… 「スクールランブルフォーエバー」]`であることがわかりました。
次に長いのは`銀魂`の`仕事のグチは家でこぼさず外でこぼせ!って言うからちょっとこぼさせてもらうけどね「侍の国」僕らの国がそう呼ばれていたのは今は昔の話…とか言って始まったこのアニメもはや一年半△あんな事こんな事いろんな事があったよね△で、そろそろ色々振り返ってもいいかなーと思ったのに「チェッ、なんだよ総集編かよ、手抜きじゃね?」とかアニメだって作るの大変なんだから文句言うのやめなさい!`です。

各話名中に改行を挟むことはできませんでしたので、1行あたりの文字数が増えた遠因となっている可能性があります。

4.4.6. commas列の深堀り#

commas(読点数)について深堀りします。 読点とは、「」のことです。

まず、本書全体の読点数を確認してみましょう。

Hide code cell content
# commas列の合計値を算出
df_draft["commas"].sum()
12280

果たして多いのか少ないのかわかりません。そこで、以下のように集計方針を修正します:

  • 文字数と比較する

  • 読点()だけでなく、句点()も合算する

Hide code cell content
# 本書の文字数を句読点数で割る
df_draft["chars"].sum() / (df_draft["commas"].sum() + df_draft["periods"].sum())
39.55394684648949

句読点あたり平均約40文字が存在するようです。 もちろん内容に依存するため一概には言い切れませんが、読点は40文字を目安に打つと良いとする記事もあります。 数値としては、そこまで違和感のある結果では無さそうです。

また、1文あたりの読点数も気になります。これは読点数(commas)を句点数(periods)で割ることで算出できます。

Hide code cell content
# 本書の読点数を句点数で割る
df_draft["commas"].sum() / df_draft["periods"].sum()
1.5567951318458417

1文あたり(1句点あたり)平均1.6程度の読点を打っているようです。 本ブログの文章も、だいたいその程度のようです。 違和感はありません。

では、読点が特に多い章はどれでしょうか?

Hide code cell content
# commas列を基準に降順ソートし、トップ5を表示
df_draft.sort_values("commas", ascending=False).head()
vol sec chars lines commas periods size fns images codes bolds
12 vol2 04 85244 3276 1312 854 186358 38 158 91 86
4 vol1 04 78336 3033 1260 751 173095 27 159 75 126
14 vol2 06 75179 2819 1238 720 167637 10 131 65 98
11 vol2 03 76151 3121 1138 724 163507 18 152 93 80
10 vol2 02 63862 2423 962 651 140348 20 140 73 68

chars(文字数)が最も多いvol2(下巻)の04(4章)において、最もcommas(句点数)が多かったようです。

では、1文あたりの読点数が多い章はどうでしょうか?

Hide code cell content
# 章ごとにcommasをperiodsで割った列を作成し、それに基づいて降順ソート
# headメソッドでトップ5のみ表示
df_draft.assign(**{"commas/periods": lambda x: x["commas"] / x["periods"]}).sort_values(
    "commas/periods", ascending=False
).head()
vol sec chars lines commas periods size fns images codes bolds commas/periods
1 vol1 01 28799 1005 703 396 70533 41 60 0 72 1.775253
14 vol2 06 75179 2819 1238 720 167637 10 131 65 98 1.719444
13 vol2 05 62972 2149 716 418 121437 15 60 67 51 1.712919
4 vol1 04 78336 3033 1260 751 173095 27 159 75 126 1.677763
2 vol1 02 35034 1340 893 540 86916 25 110 1 105 1.653704

1文あたりの読点が最も多かったのは、vol1(上巻)の01(1章)のようです。 上巻の1章ではデータ可視化の導入として、周辺領域の基本知識を網羅的に列挙します。 筆者の筆力では噛み砕いて説明することが難しい概念も登場するため、冗長な文章になってしまったのかもしれません。

4.4.7. periods列の深堀り#

periods(句点数)について深堀りします。句点とは「」のことです。

まず、本書全体の句点数を集計してみます。

Hide code cell content
# periods列の合計値を算出
df_draft["periods"].sum()
7888

日本語では、句点は文章の区切りを表します。 つまり、上記の結果は、本書中に7888文存在することを表します。

1文あたりの文字数が気になります。集計してみましょう。

Hide code cell content
# chars列の合計値をperiods列の合計値で割る
df_draft["chars"].sum() / df_draft["periods"].sum()
101.13133874239351

1文あたり、平均100文字以上存在するようです。 1文の長さは40-60文字がベストという記事もありますので、長すぎる気がします。

原因を追求するため、章ごとに同様の集計結果を見てみましょう。 1文あたりの文字数が多いトップ5章をリストアップします。

Hide code cell content
# charsをperiodsで割った新たな列を作成し、それを基準に降順ソート
# headメソッドでトップ5件のみ表示
df_draft.assign(**{"chars/periods": lambda x: x["chars"] / x["periods"]}).sort_values(
    "chars/periods", ascending=False
).head()
vol sec chars lines commas periods size fns images codes bolds chars/periods
13 vol2 05 62972 2149 716 418 121437 15 60 67 51 150.650718
5 vol1 05 79409 3611 925 638 151478 27 106 225 56 124.465517
6 vol1 06 59224 2574 725 478 110744 18 89 100 24 123.899582
3 vol1 03 46086 1872 629 396 91200 18 58 51 43 116.378788
11 vol2 03 76151 3121 1138 724 163507 18 152 93 80 105.180939

vol2(下巻)の05(5章)において、最も1文あたりの文字数が多いようです。 前述したように、下巻の5章では箇条書きを多用しています。 筆者のこだわりとして、 箇条書きの末尾には句点をつけない ようにしています。 加えて、非常に長いマンガ作品名やマンガ作品名を取り扱っているのも下巻5章です。

また、コードブロックにおいても、句読点は少なくなる傾向があります。 vol1(上巻)の05(5章)では非常に多くのコードブロックが存在するため、 比較的1文あたりの文字数が多くなっているのかもしれません。

以上から、厳密には、コードブロックや箇条書きを除外した文字数を分析対象とするべきです。 今回は私の稼働を確保できなかったため、簡易な分析にとどめました。

4.4.8. size列の深堀り#

size(ファイルサイズ)について深堀りします。 ちなみに、本稿における「ファイルサイズ」とは、

with open(file_path, "r") as f:
    size = file_path.stat().st_size

のようにstat().st_sizeによって取得したものを指します。 詳細は草稿の統計情報の取得を参照ください。

まず、ファイルサイズが大きい章を確認してみましょう。

Hide code cell content
# size列を基準に降順ソートし、トップ5を表示
df_draft.sort_values("size", ascending=False).head()
vol sec chars lines commas periods size fns images codes bolds
12 vol2 04 85244 3276 1312 854 186358 38 158 91 86
4 vol1 04 78336 3033 1260 751 173095 27 159 75 126
14 vol2 06 75179 2819 1238 720 167637 10 131 65 98
11 vol2 03 76151 3121 1138 724 163507 18 152 93 80
5 vol1 05 79409 3611 925 638 151478 27 106 225 56

想定通り、chars(文字数)が大きい章が並んでいます。

では、文字数と比較した相対的なファイルサイズでソートしてみましょう。

Hide code cell content
# sizeをcharsで割った列を作成し、それに基づき降順ソート
# headメソッドで上位5章を表示
df_draft.assign(**{"size/chars": lambda x: x["size"] / x["chars"]}).sort_values(
    "size/chars", ascending=False
).head()
vol sec chars lines commas periods size fns images codes bolds size/chars
2 vol1 02 35034 1340 893 540 86916 25 110 1 105 2.480904
1 vol1 01 28799 1005 703 396 70533 41 60 0 72 2.449148
7 vol1 07 38569 1359 708 550 88808 23 87 17 71 2.302575
0 vol1 00 13754 562 248 184 31233 6 7 13 5 2.270830
14 vol2 06 75179 2819 1238 720 167637 10 131 65 98 2.229838

vol1(上巻)の02(2章)がトップであることがわかりました。 上位5章の顔ぶれを見ると、codes(コードブロック数)が少ないものが目立ちます。

ここからは仮説です。 一般にコードブロックで用いられる英数字は1文字あたり1バイトですが、日本語などのマルチバイト文字は1文字あたり2〜4バイトを必要とします。 つまり、日本語を用いた場合、同じ文字数でもファイルサイズが大きくなります。 以上から、コードブロックの少ない章ではマルチバイト文字が支配的となり、1文字あたりのファイルサイズが大きくなってしまったのではないでしょうか。

4.4.9. fns列の深堀り#

fns(脚注数)について深堀りします。 ちなみに、本稿における「脚注数」とは、

fns = content.count("</fn>")

のように</fn>の数を指します。 詳細は草稿の統計情報の取得を参照ください。

脚注が多い章を確認してみましょう。

Hide code cell content
# fns列を基準に降順ソートし、上位5件を表示
df_draft.sort_values("fns", ascending=False).head()
vol sec chars lines commas periods size fns images codes bolds
1 vol1 01 28799 1005 703 396 70533 41 60 0 72
12 vol2 04 85244 3276 1312 854 186358 38 158 91 86
4 vol1 04 78336 3033 1260 751 173095 27 159 75 126
5 vol1 05 79409 3611 925 638 151478 27 106 225 56
2 vol1 02 35034 1340 893 540 86916 25 110 1 105

vol1(上巻)の01(1章)に最も多くの脚注が存在するようです。

これは技術評論社の指定するマークダウンの特徴ですが、 参考文献は脚注内で記載 する必要があります。 例えば、以下は1.1節中の一部です。

## データ可視化の美しさと正しさ

データ可視化のもう一つの大きな特徴は、芸術的な側面と科学的な側面を併せ持つ
<fn>
参考文献:Claus O Wilke、小林 儀匡、瀬戸山 雅人「データビジュアライゼーションの基礎 ―明確で、魅力的で、説得力のあるデータの見せ方・伝え方」(オライリージャパン、2022)
</fn>
ことです。

上巻1章では、データ可視化に関する基本知識を網羅的に扱うため、多様な文献を引用します。 これにより脚注数が増えた可能性があります。

では、文字数あたりの脚注数はいかがでしょうか?

Hide code cell content
# fnsをcharsで割った列を作成し、その列を基準に降順ソート
# headメソッドで上位5件を表示
df_draft.assign(**{"fns/chars": lambda x: x["fns"] / x["chars"]}).sort_values(
    "fns/chars", ascending=False
).head()
vol sec chars lines commas periods size fns images codes bolds fns/chars
1 vol1 01 28799 1005 703 396 70533 41 60 0 72 0.001424
15 vol2 appendix 2207 119 29 21 4509 2 0 4 5 0.000906
2 vol1 02 35034 1340 893 540 86916 25 110 1 105 0.000714
7 vol1 07 38569 1359 708 550 88808 23 87 17 71 0.000596
12 vol2 04 85244 3276 1312 854 186358 38 158 91 86 0.000446

今回も上巻1章がトップとなりました。

4.4.10. images列の深堀り#

images(画像数)列を深堀りします。 ちなみに、本稿における「画像数」とは、

content.count("![")

のように![(画像タグ)の数を指します。 詳細は草稿の統計情報の取得を参照ください。

まず、画像数が多い章を確認してみましょう。

Hide code cell content
# image列を基準に降順ソートし、上位5件を表示
df_draft.sort_values("images", ascending=False).head()
vol sec chars lines commas periods size fns images codes bolds
4 vol1 04 78336 3033 1260 751 173095 27 159 75 126
12 vol2 04 85244 3276 1312 854 186358 38 158 91 86
11 vol2 03 76151 3121 1138 724 163507 18 152 93 80
10 vol2 02 63862 2423 962 651 140348 20 140 73 68
14 vol2 06 75179 2819 1238 720 167637 10 131 65 98

vol1(上巻)の04(4章)において最も画像が添付されたようです。 上巻の4章では、マンガデータの様々な可視化結果を紹介しています。 納得感のある結果です。

では、章内の文字数に対する画像数の比率はどうでしょうか?

Hide code cell content
# images列をchars列で割った列を作成し、それに基づいて降順ソート
# headメソッドでトップ5件を表示
df_draft.assign(**{"images/chars": lambda x: x["images"] / x["chars"]}).sort_values(
    "images/chars", ascending=False
).head()
vol sec chars lines commas periods size fns images codes bolds images/chars
2 vol1 02 35034 1340 893 540 86916 25 110 1 105 0.003140
9 vol2 01 39984 1656 656 414 88378 14 100 45 47 0.002501
7 vol1 07 38569 1359 708 550 88808 23 87 17 71 0.002256
10 vol2 02 63862 2423 962 651 140348 20 140 73 68 0.002192
1 vol1 01 28799 1005 703 396 70533 41 60 0 72 0.002083

この基準では、vol2(下巻)の02(2章)がトップとなりました。 下巻の2章では、分布を見るための可視化手法の利用例をマンガ、アニメ、ゲームデータを例に紹介しています。

4.4.11. codes列の深堀り#

codes(コードブロック数)について深堀りします。 ちなみに、本稿における「コードブロック数」とは、

codes = content.count("```") // 2

のように```を2で割った値を指します。 詳細は草稿の統計情報の取得を参照ください。

まずは、コードブロック数の多い章を確認してみましょう。

Hide code cell content
# codes列を基準に降順ソートし、上位5件を表示
df_draft.sort_values("codes", ascending=False).head()
vol sec chars lines commas periods size fns images codes bolds
5 vol1 05 79409 3611 925 638 151478 27 106 225 56
6 vol1 06 59224 2574 725 478 110744 18 89 100 24
11 vol2 03 76151 3121 1138 724 163507 18 152 93 80
12 vol2 04 85244 3276 1312 854 186358 38 158 91 86
4 vol1 04 78336 3033 1260 751 173095 27 159 75 126

vol1(上巻)の05(5章)が最も多いようです。 前述しましたが、上巻の5章ではPython、Pandas、Plotlyの基礎を紹介します。 比較的コードブロック数が多くなるのは納得感のある結果です。

では、文字数あたりのコードブロック数という観点ではどうなるでしょうか?

Hide code cell content
# codes列をchars列で割った列を作成し、それに基づいて降順ソート
# headメソッドで上位5件を表示
df_draft.assign(**{"codes/chars": lambda x: x["codes"] / x["chars"]}).sort_values(
    "codes/chars", ascending=False
).head()
vol sec chars lines commas periods size fns images codes bolds codes/chars
5 vol1 05 79409 3611 925 638 151478 27 106 225 56 0.002833
15 vol2 appendix 2207 119 29 21 4509 2 0 4 5 0.001812
6 vol1 06 59224 2574 725 478 110744 18 89 100 24 0.001689
11 vol2 03 76151 3121 1138 724 163507 18 152 93 80 0.001221
10 vol2 02 63862 2423 962 651 140348 20 140 73 68 0.001143

この観点でも、上巻の5章がトップとなりました。

4.4.12. bolds列の深堀り#

bolds(太字数)について深堀りします。 ちなみに、本稿における「太字数」とは、

bolds = content.count("**") // 2

のように**を2で割った値を指します。 詳細は草稿の統計情報の取得を参照ください。

まず、太字が最も多く登場する章を確認してみましょう。

Hide code cell content
# bolds列に基づいて降順ソートし、headで上位5件を表示
df_draft.sort_values("bolds", ascending=False).head()
vol sec chars lines commas periods size fns images codes bolds
4 vol1 04 78336 3033 1260 751 173095 27 159 75 126
2 vol1 02 35034 1340 893 540 86916 25 110 1 105
14 vol2 06 75179 2819 1238 720 167637 10 131 65 98
12 vol2 04 85244 3276 1312 854 186358 38 158 91 86
11 vol2 03 76151 3121 1138 724 163507 18 152 93 80

太字が最も多く登場するのは、vol1(上巻)の04(4章)のようです。 前述しましたが、上巻4章ではマンガデータを用いた多様な可視化例を紹介します。

では、文字数あたりの太字数はいかがでしょうか?

Hide code cell content
# bolds列をchars列で割った列を作成し、それに基づいて降順ソート
# headメソッドで上位5件を表示
df_draft.assign(**{"bolds/chars": lambda x: x["bolds"] / x["chars"]}).sort_values(
    "bolds/chars", ascending=False
).head()
vol sec chars lines commas periods size fns images codes bolds bolds/chars
2 vol1 02 35034 1340 893 540 86916 25 110 1 105 0.002997
1 vol1 01 28799 1005 703 396 70533 41 60 0 72 0.002500
15 vol2 appendix 2207 119 29 21 4509 2 0 4 5 0.002266
7 vol1 07 38569 1359 708 550 88808 23 87 17 71 0.001841
4 vol1 04 78336 3033 1260 751 173095 27 159 75 126 0.001608

文字数に対して太字が最も多く登場するのは、vol1(上巻)の02(2章)のようです。 上巻2章では、データ可視化に関する基本的な概念に関して詳細に解説します。 太字による強調が目立つのも納得できます。

4.5. 基礎分析のまとめ#

本章では、本書の執筆時間、執筆期間中の睡眠時間、そして草稿の統計情報の3つのデータセットに対して基礎分析を行いました。 得られた主な知見は以下の通りです。

  1. 執筆時間

    • 合計時間は 1161時間50分38秒

    • 2023年末から2024年前半、2025年12月から2026年1月に作業時間がとくに増加

    • 長期休暇を取りやすい月や本業に余裕のある3-5月の合計作業時間が長い

    • 想定通り、土日の合計作業時間が長い

  2. 睡眠時間

    • 執筆期間中、ほぼ毎日の睡眠時間を記録している

    • 2019年2月23日から2026年1月22日までの期間を対象としており、執筆時間のデータよりも広い

  3. 草稿

    • 下巻(vol2)のほうが上巻(vol1)よりもボリュームが大きい

    • 全体として、1行あたり約25文字、1文あたり約100文字である

    • 句読点に着目すると、句読点あたり約40文字、1文あたり約1.6個の読点が存在する

    • コードブロックが少ない章において、1文字あたりのファイルサイズが大きい傾向にある

    • 章別の各種集計結果は、基本的に直感に反しない

以上の知見は、本書の執筆プロセスや内容の特徴を定量的に表しています。 次章以降では、これらのデータを様々な観点から可視化します。