5 利益分布アプローチによる利益マネジメントの実態分析

Published

2026/05/08

Modified

2026/04/26

1 この回で新たに学ぶ関数

今回はデータフレームの結合から統計量の集計,ヒストグラムの描画・保存まで一気に行う.新しく登場する関数が多いので,まずは全体像を把握しておこう.

関数 パッケージ 役割
full_join() / inner_join() / left_join() dplyr 二つのデータフレームを結合する
if_else() dplyr 条件に応じて異なる値を返す
quantile() base R 分位点(四分位など)を求める
round() base R 数値を指定した桁数で丸める
across() + where() dplyr 複数列に同じ処理を一括適用する
write_csv() readr データフレームをCSVファイルに出力する
seq() base R 等差数列を生成する
geom_histogram() ggplot2 ヒストグラムを描画する
geom_vline() ggplot2 グラフに垂直線を追加する
ggsave() ggplot2 直前に描画したグラフをファイルに保存する
lag() dplyr 一つ前の行の値を取得する(練習問題)
  • 前回までに学んだmutate()group_by()summarize()drop_na()なども引き続き使用する.
  • 迷ったらこの表に戻って「今どの関数を使っているのか」を確認しよう.

2 join系関数(教科書コラム5.2)

2.1 二つのデータフレームの結合

  • 実証会計・ファイナンスでは,財務データと株式データの結合を典型例として,二つのデータフレームを結合する場面にしばしば遭遇する.
  • dplyrにはfull_join()関数を始め,結合を行うために用いるjoin系関数が用意されている.
  • 以下では,株価データAとDPSデータBの結合を通じて,それぞれのjoin系関数の返り値を確認していこう.
Code
# tidyverseの読み込み
pacman::p_load(tidyverse) 

# 例に用いる二つのデータフレームを作成
A <- tibble(firm_ID = c(1, 2),
            stock_price = c(120, 500)) # 株価データが格納されたAを作成
B <- tibble(firm_ID = c(1, 3),
            DPS = c(5, 10)) # DPSデータが格納されたBを作成
  • 上のコードでは,tibble()関数を使って二つのデータフレームを手動で作成している.

2.2 完全外部結合full_join()関数


2.3 内部結合inner_join()関数

Code
# inner_join()関数による結合
A %>% inner_join(B, by = "firm_ID") 


2.4 左結合left_join()関数

Code
# left_join()関数による結合
A %>% left_join(B, by = "firm_ID") 

3 分布の不連続性(教科書コラム5.3)

3.1 成績評価チート


3.2 その他の例

  • 大相撲の勝ち星
  • 食べログ3.6問題

4 分析の準備

4.1 財務データの読み込み

  • 会計利益の分布に果たして歪みがあるかを検証を進めるため,まずは分析に利用する財務データを読み込むことから始めよう.
Note目標
  • 目標1: simulation_dataフォルダにあるfinancial_data.csvをデータフレームfinancial_dataとして読み込んでみよう.
  • 目標2: その後,head()関数により,financial_dataの冒頭6行を表示し,どのようなデータが収録されているか確認してみよう.

4.2 フォルダ構造

  • 現在地がcodesである場合,読み込みたいfinancial_data.csvが格納されているsimulation_dataフォルダにアクセスするには,一個上の階層に一度戻る必要があり,それは..により実現可能である.
  • あとは,simulation_dataフォルダに移動 (/simulation_data)し,/financial_data.csvで目的のファイルにアクセス可能である.


4.3 目標を達成するためのコード例

Code
# 財務データの読み込み
financial_data <- read_csv("../simulation_data/financial_data.csv") 

# head()関数を用いて冒頭6行の結果のみ表示
head(financial_data) 
  • fiscal_year_end: 決算年月 (YYYY-MM-DD形式)
  • macc: 決算月数
  • X: 当期純利益(百万円)
  • TA: 資産合計(百万円)
  • CFO: 営業活動によるキャッシュフロー(百万円)

4.4 当期純利益Xを基準化

  • 分析にあたっては,当期純利益Xそのものの分布ではなく,各企業の規模を統制して各観測値を横並びで比較可能にしたScaled Earnings (SE)の分布を考えよう.

    \[ \underbrace{SE_{i,t}}_{\textbf{企業$i$の年度$t$のScaled Earnings}} = \frac{\overbrace{X_{i,t}}^{\textbf{企業$i$の年度$t$の当期純利益}}}{\text{各企業の規模の代理変数}} \]

  • この分析では,簡便的に各企業の発行する株式の時価総額 (\(=\) 株価 \(\times\) 発行済株式数)を規模の代理変数と捉え,分析を進めて行こう.


4.5 株式データの読み込み

  • Scaled Earningsのデフレータとなる時価総額を得るため,simulation_dataフォルダにあるstock_data.csvstock_dataとして読み込んでみよう.
Code
# 株式データの読み込み
stock_data <- read_csv("../simulation_data/stock_data.csv") 
head(stock_data)
  • stock_price: 株価
  • shares_outstanding: 発行済株式数

4.6 stock_dataに時価総額ME列の追加

Code
# mutate()関数を使ってME列の追加
stock_data <- stock_data %>% 
  mutate(ME = (stock_price * shares_outstanding) / 1e6) # MEを百万円単位で計算 

4.7 単位確認の重要性

  • 先のコードで登場した1e6は科学技術分野で一般的に用いられる科学的表記と呼ばれる表記法(教科書157頁)であり,\(1 \times 10^6 (= 1,000,000)\)と等しい.
  • 会計・ファイナンス研究で頻用される財務データは,一般的に百万円単位でデータが収録されているため,MEを計算する際,財務データと単位を揃えることを目的として1e6で除している.
  • (stock_price * shares_outstanding) / 1000000とはしないこと!

4.8 財務データと株式データの結合


4.9 パイプ演算子を繋げてSE列も追加

Code
# 財務データと株式データの結合し,SE列も追加
financial_data <- financial_data %>% 
  left_join(stock_data, by = c("firm_ID", "year")) %>% 
  mutate(SE = if_else(macc == 12, X / ME, NA)) 
    # 決算月数が12ヶ月ではないものは欠損値に
  • 上のコードでは,dplyrif_else()関数を使って決算月数が12ヶ月の場合はSEを計算し,そうでなければ欠損値NAになるように工夫している.


5 利益分布アプローチによる分析

5.1 サンプルの確定

  • データの前処理が完了すれば,分析対象を確定させ,新たなデータフレームanalysis_sampleを利用して分析を進めて行こう.
Note目標
  • ここでは,SEが欠損値となっている観測値を除外(\(=\) SEが計算可能な観測値のみを抽出)し,分析対象となるデータフレームをanalysis_sampleとして定義してみよう.

5.2 目標を達成するためのコード例

Code
# 分析に利用する観測値のみのデータフレームanalysis_sampleを作成
analysis_sample <- financial_data %>%
  drop_na(SE) # SEが欠損値のものを削除
  • drop_na()の使い方は,ここを参照.

5.3 集計作業の実践 — summarize()関数を使ってみよう

Note目標
  • データフレームanalysis_sampleを用いて,年度ごとにSEの平均値を算出し,それをMean列と名付けよう.
  • こうして出来たyear列とMean列から成るデータフレームをtable_1として定義しよう.

5.4 目標を達成するためのコード例

Code
# 年度ごとにSEの平均値を計算
table_1 <- analysis_sample %>% # 集計結果をtable_1として定義
  group_by(year) %>% # 年度でグループ化
  summarize(Mean = mean(SE)) # 平均値をMeanと命名

head(table_1) # 内容の確認
  • 2行目: 以下の処理により作成されるデータフレームをtable_1として定義.
  • 3行目: 年度yearでグループ化.
  • 4行目: summarize関数を適用し,SEの平均値をmean(SE)により計算し,それをMeanと命名.

5.5 更に一歩進んで,年度ごとに基本統計量を集計

Code
# 年度ごとに基本統計量を集計
table_1 <- analysis_sample %>%
  group_by(year) %>% # 年度毎にグループ化
  summarize(N = n(), # 観測値数
            Mean = mean(SE), # 平均値
            SD = sd(SE), # 標準偏差
            Q1 = quantile(SE, 0.25), # 第1四分位
            Median = median(SE), # 中央値
            Q3 = quantile(SE ,0.75)) # 第3四分位


5.6 quantile()関数の使い方

  • 分位点を求めるにはquantile()関数を用いる.この関数は第一引数に入力データ(数値ベクトル),第二引数に求めたい分位点の値をパーセントでなく小数表示で代入する(教科書185頁)
  • 例えば,SEの第1四分位を求めたいならば,quantile(SE, 0.25)とすれば良い.


5.7 出力結果のアレンジ — 桁数の調整

Code
# 年度ごとに基本統計量を集計(平均値のみ桁数調整)
table_1 <- analysis_sample %>%
  group_by(year) %>% # 年度毎にグループ化
  summarize(N = n(), # 観測値数
            Mean = round(mean(SE), 3), # 平均値
            SD = sd(SE), # 標準偏差
            Q1 = quantile(SE, 0.25), # 第1四分位
            Median = median(SE), # 中央値
            Q3 = quantile(SE, 0.75)) # 第3四分位


5.8 一気に出力結果を調整する方法

Code
# table_1の数値列を小数点第3位まで丸め直す
table_1 <- table_1 %>%
  mutate(across(where(is.double), ~ round(.x, 3)))

head(table_1)
  • across() は「複数の列に対して同じ処理を一括で適用する」ための関数である.
  • where(is.double) は「小数を含む数値型<dbl>の列をすべて選択する」という意味である.このため,整数型<int>N列は対象にならず,平均Meanや標準偏差SDなどの列だけが処理される.
  • ~ round(.x, 3) の部分は「無名関数(ラムダ関数)」と呼ばれる書き方である.
    • ~ の後に書いた式が「その列に対してやる処理」を表している.
    • .x は「今処理している列の値」を指している(このように一時的に処理対象を表す記号をプレースホルダーという).
    • ここでは「across()関数で指定された各列を小数点第3位まで丸める」という意味になる.

5.9 write_csv()関数を使ったデータフレームの出力

Code
# table_1の結果を出力
write_csv(table_1, "../tables/table_1.csv")
  • readrwrite_csv()関数を使って,先に作成したデータフレームtable_1tablesフォルダに出力しよう.
  • write_csv()関数の第一引数はデータフレーム名を入力し,第二引数でファイル名を指定する(教科書184頁)
  • 現在地がcodesであることを前提にすれば,出力したいtablesフォルダへ移動するには,一個上の階層に一度戻る必要があり,それは..により実現可能である.
  • あとは,tablesフォルダに移動 (/tables)し,table_1.csvという名前で出力 (/table_1.csv)すれば良いので,write_csv()関数の第二引数は,"../tables/table_1.csv"と指定する.

5.10 Scaled Earnings (SE)のヒストグラム

  • 締めくくりとして,ggplot2を利用し,Scaled Earnings (SE)のヒストグラムを描画し,利益マネジメントの実態を明らかにしていこう.
Note目標
  • データフレームanalysis_sample内のSEについて,-0.2から0.2までのSEのヒストグラムを描画しよう.
    • ビン幅は0.005とする.
    • \(x\)軸のラベルはEarnings Interval\(y\)軸のラベルはFrequencyとする.
    • (余力がある人は)Burgstahler and Dichev (1997)同様,\(x = 0\)の破線を引き,ベンチマークが一目瞭然で分かるように調整.

5.11 オリジナル論文のヒストグラム (Fig. 3)

5.12 SEのヒストグラムの描画

Code
# SEのヒストグラムの描画
ggplot(analysis_sample) +
  geom_histogram(aes(x = SE),
                 breaks = seq(-0.2, 0.2, 0.005)) +
  labs(x = "Earnings Interval", y = "Frequency") + 
  scale_y_continuous(expand = c(0, 0)) +
  theme_classic()

5.13 \(x = 0\)を表す破線の追加

  • geom_vline()関数は,vertical(垂直)に直線を引く.xintercept引数で値を指定し,またlinetype引数で適当な線種を指定する(dashedの他にも,dottedsolidなど多様にオプションが用意されている).
Code
# SEのヒストグラムの描画
ggplot(analysis_sample) +
  geom_histogram(aes(x = SE),
                 breaks = seq(-0.2, 0.2, 0.005)) +
  labs(x = "Earnings Interval", y = "Frequency") + 
  scale_y_continuous(expand = c(0, 0)) +
  geom_vline(xintercept = 0, linetype = "dashed") + 
  theme_classic()

5.14 完成したグラフをPNG形式で出力

Note目標
  • こうして描画されたヒストグラムをfiguresフォルダにfigure_1.pngの名前を付して出力しよう.
  • (ヒント1) 直前に出力したグラフを保存したい場合,ggsave()関数を利用する.第一引数にはファイル名を指定しよう.
  • (ヒント2) figuresフォルダのある階層を意識して第一引数を指定しよう.

5.15 目標を達成するためのコード例

Code
# ggsave()関数を使ってSEのヒストグラムをfigure_1.pngとして出力
ggsave("../figures/figure_1.png") 
  • 必要に応じてグラフのサイズなどの細部を調整したい場合は,別途引数により調整すれば良い.
Code
# 出力サイズを明示的に指定して出力
ggsave("../figures/figure_1.png", 
       width = 20, # 幅を指定
       height = 10, # 高さを指定
       units = "cm") # 幅と高さの単位を指定

6 練習問題

6.1 損失回避期間と利益マネジメントのインセンティブ

  • Burgstahler and Dichev (1997)や首藤 (2010)1では,過去において損失回避期間が長い企業の経営者ほど,損失を回避するインセンティブが高まり,分布の歪みがより顕著に現れることが明らかにされている.
  • ここでは,その検証結果の再現を試みる一環として,一期前のSEが正で少なくとも過去1期は損失回避できた企業群だけに絞り,同様のヒストグラムを描画する方法を実践していこう.

6.2 前年度損失回避企業のみを抽出して描画

NoteExercise
  • 目標1: 最初にmutate()関数とlag()関数を組み合わせて,一期前のSEを表すlagged_SEをデータフレームanalysis_sampleに追加しよう.
    • (注意点1) パネルデータでlag()関数を使う場合は,必ずgroup_by(firm_ID)で企業ごとにグループ化してから適用すること.グループ化せずにlag()を使うと,異なる企業の前行の値を取得してしまう.
    • (注意点2) 一行前が必ずしも一期前とは限らないため,if_else()関数を用いてyearlag(year)との関係を示す条件式を予め指定し,lagged_SEの計算式を工夫するのが理想的である.
  • 目標2: lagged_SE列が追加できれば,前年度損失回避企業をfilter()関数により抽出し,パイプ演算子を使って抽出データをggplot()関数に引き渡して一気にヒストグラムの可視化まで行ってみよう.

7 自習課題

  • 以下の課題に取り組み,今回学んだ内容の理解度を自分で確認しよう.
  • 課題では,講義で作成したfinancial_datastock_data,およびanalysis_sampleをそのまま使用する.

7.1 準備:自分だけの分析条件を生成する

  • 以下のコードをRコンソールで実行しよう.set.seed()関数の引数には,自分の学籍番号の末尾4桁の数字(末尾の英字を除く)を入力すること.
Code
# 自分の学籍番号の末尾4桁の数字を入力(例: 学籍番号が2001234Bの場合は1234)
set.seed(1234)

# 自分だけの分析条件を生成
my_year   <- sample(2010:2018, 1)                   # 分析対象の年度
my_breaks <- sample(c(0.002, 0.005, 0.008, 0.01), 1) # ヒストグラムのビン幅
my_threshold <- round(runif(1, min = 0.01, max = 0.05), 3) # サブサンプルの閾値

cat("対象年度:", my_year, "\n")
cat("ビン幅:", my_breaks, "\n")
cat("閾値:", my_threshold, "\n")

7.2 Q1: inner_join()full_join() の違い

Note問題
  • 講義で読み込んだfinancial_datastock_dataを,inner_join()で結合した場合とfull_join()で結合した場合のそれぞれについて,nrow()で行数を報告せよ.
  • 両者の行数に差が生じる理由を一文で説明せよ.

7.3 Q2: summarize() + across() による記述統計の作成

Note問題
  • データフレームanalysis_sampleから,準備で生成したmy_year年度のデータのみをfilter()で抽出せよ.
  • 抽出したデータについて,\(SE\)の基本統計量(\(N\), \(\text{Mean}\), \(SD\), \(Q1\), \(\text{Median}\), \(Q3\))をsummarize()で計算し,across(where(is.double), ~ round(.x, 3))で小数点第3位まで丸めよ.
  • my_year年度の\(\text{Mean}\)の値を報告せよ.

7.4 Q3: geom_histogram() + geom_vline() によるヒストグラム

Note問題
  • データフレームanalysis_sample\(SE\)について,\(-0.2\)から\(0.2\)の範囲で,準備で生成したmy_breaksをビン幅とするヒストグラムをgeom_histogram()で描画せよ.\(x\)軸ラベルはEarnings Interval\(y\)軸ラベルはFrequencyとすること.
  • geom_vline()を使って\(x = 0\)に破線を追加せよ.
  • 講義で使用したビン幅(\(0.005\))とmy_breaksとでヒストグラムの見え方がどう異なるか,一文で答えよ.

7.5 Q4: 条件付きサブサンプルの分析

Note問題
  • データフレームanalysis_sampleに対し,group_by(firm_ID)の上でlag()if_else()を用いて前年度の\(SE\)を表すlagged_SE列を追加せよ(講義の練習問題と同様に,yearlag(year)の差が1であることを確認すること).
  • lagged_SEが準備で生成したmy_threshold以上の企業(\(=\) 前年度に少なくともmy_threshold以上の利益を確保していた企業)のみをfilter()で抽出し,\(SE\)のヒストグラムを描画せよ.ビン幅は\(0.005\),範囲は\(-0.2\)から\(0.2\)とする.
  • 全サンプルのヒストグラム(Q3で描画したもの)と比較して,\(SE = 0\)付近の分布の形状にどのような違いが見られるか,一文で答えよ.

(ヒント!)

  1. lagged_SEの作成には,mutate(lagged_SE = if_else(year - lag(year) == 1, lag(SE), NA))のようにif_else()で年度の連続性を確認するのがポイントである.
  2. Q3とQ4のヒストグラムを並べて見比べると,損失回避インセンティブの効果がより明確に観察できる.

Footnotes

  1. 首藤昭信 (2010)『日本企業の利益調整: 理論と実証』中央経済社.↩︎