Skip to contents

Overview

Fundamental analysis evaluates a company’s financial health, operational profitability, balance sheet strength, and market valuation multiples. In this guide, you will build an end-to-end fundamental analysis and stock screening workflow using yahoofinancer:

  1. Extract Financial Statements: Download income statements, balance sheets, and cash flow statements across multiple peer companies using yf_get_financials().
  2. Compute Core Ratios: Calculate profitability margins (operating margin, net profit margin) and cash generation metrics (free cash flow conversion).
  3. Inspect Valuation Multiples: Retrieve quarterly valuation ratios (P/E, forward P/E, PEG, EV/EBITDA, P/B) with Tickers$valuation_measures.
  4. Build a Fundamental Screener: Combine statement metrics and valuation multiples into a tidy scorecard to filter and rank candidates.
  5. Visualize Peer Comparisons: Compare financial performance and valuation multiples using ggplot2.

Required Packages

# Install required packages if needed:
# install.packages(c("yahoofinancer", "dplyr", "tidyr", "ggplot2", "scales"))

library(yahoofinancer)
library(dplyr)
library(tidyr)
library(ggplot2)
library(scales)

1. Downloading Financial Statements

The functional helper yf_get_financials() retrieves tidy financial statements across single or multiple tickers. Statements are available at "annual" (default) or "quarterly" frequencies.

Income Statement

peer_tickers <- c("AAPL", "MSFT", "GOOG")

income_df <- yf_get_financials(
  tickers        = peer_tickers,
  statement_type = "income",
  frequency      = "annual"
)

income_df |>
  select(symbol, date, period_type, total_revenue, operating_income, net_income) |>
  head(n = 6)
#> # A tibble: 6 x 6
#>   symbol date       period_type total_revenue operating_income   net_income
#>   <chr>  <date>     <chr>               <dbl>            <dbl>        <dbl>
#> 1 AAPL   2021-09-25 12M          365817000000     108949000000  94680000000
#> 2 AAPL   2022-09-24 12M          394328000000     119437000000  99803000000
#> 3 AAPL   2023-09-30 12M          383285000000     114301000000  96995000000
#> 4 MSFT   2022-06-30 12M          198270000000      83383000000  72738000000
#> 5 MSFT   2023-06-30 12M          211915000000      88523000000  72361000000
#> 6 MSFT   2024-06-30 12M          245122000000     109433000000  88136000000

Cash Flow Statement

Cash flow statements provide visibility into actual cash generated from operations and free cash flow after capital expenditures:

cashflow_df <- yf_get_financials(
  tickers        = peer_tickers,
  statement_type = "cash-flow",
  frequency      = "annual"
)

cashflow_df |>
  select(symbol, date, operating_cash_flow, capital_expenditure, free_cash_flow) |>
  head(n = 6)
#> # A tibble: 6 x 5
#>   symbol date       operating_cash_flow capital_expenditure free_cash_flow
#>   <chr>  <date>                   <dbl>               <dbl>          <dbl>
#> 1 AAPL   2021-09-25        104038000000        -11085000000    92953000000
#> 2 AAPL   2022-09-24        122151000000        -10708000000   111443000000
#> 3 AAPL   2023-09-30        110543000000        -10959000000    99584000000
#> 4 MSFT   2022-06-30         89035000000        -23886000000    65149000000
#> 5 MSFT   2023-06-30         87582000000        -28107000000    59475000000
#> 6 MSFT   2024-06-30        118548000000        -44477000000    74071000000

Balance Sheet

Balance sheets expose liquidity and solvency positions including working capital, cash reserves, and debt levels:

balance_df <- yf_get_financials(
  tickers        = peer_tickers,
  statement_type = "balance-sheet",
  frequency      = "annual"
)

balance_df |>
  select(symbol, date, total_assets, total_stockholder_equity, working_capital, total_debt) |>
  head(n = 6)

2. Computing Profitability & Solvency Ratios

By joining the statement tables, we can compute standardized performance indicators:

  • Operating Margin: Operating Income/Total Revenue\text{Operating Income} / \text{Total Revenue}
  • Net Margin: Net Income/Total Revenue\text{Net Income} / \text{Total Revenue}
  • Free Cash Flow Conversion: Free Cash Flow/Operating Cash Flow\text{Free Cash Flow} / \text{Operating Cash Flow}
fundamentals <- income_df |>
  left_join(cashflow_df, by = c("symbol", "date", "period_type")) |>
  left_join(balance_df, by = c("symbol", "date", "period_type")) |>
  group_by(symbol) |>
  arrange(date, .by_group = TRUE) |>
  mutate(
    operating_margin = operating_income / total_revenue,
    net_margin       = net_income / total_revenue,
    fcf_conversion   = free_cash_flow / operating_cash_flow,
    debt_to_equity   = total_debt / total_stockholder_equity
  ) |>
  ungroup()

# View the most recent period for each peer
latest_fundamentals <- fundamentals |>
  group_by(symbol) |>
  slice_tail(n = 1) |>
  ungroup() |>
  select(symbol, date, total_revenue, operating_margin, net_margin, fcf_conversion, debt_to_equity)

latest_fundamentals

3. Valuation Measures Across Peers

Valuation multiples allow comparing market pricing against underlying fundamentals. Using the Tickers class, we retrieve quarterly valuation measures across the peer group:

stocks <- Tickers$new(peer_tickers)
val_measures <- stocks$valuation_measures

val_measures |>
  group_by(symbol) |>
  slice_tail(n = 1) |>
  ungroup() |>
  select(symbol, date, market_cap, pe_ratio, forward_pe_ratio, peg_ratio, enterprise_value_ebitda_ratio)
#> # A tibble: 3 x 7
#>   symbol date       market_cap pe_ratio forward_pe_ratio peg_ratio enterprise_value_ebitda_ratio
#>   <chr>  <date>          <dbl>    <dbl>            <dbl>     <dbl>                         <dbl>
#> 1 AAPL   2024-06-30    3.45e12     33.2             28.5      2.10                          24.2
#> 2 GOOG   2024-06-30    2.25e12     25.4             21.8      1.45                          17.8
#> 3 MSFT   2024-06-30    3.30e12     36.1             30.2      2.35                          25.6

4. Multi-Factor Stock Screening Scorecard

We combine the latest operational margins with current valuation multiples to create a screening scorecard:

# Extract latest valuation metrics
latest_valuation <- val_measures |>
  group_by(symbol) |>
  slice_tail(n = 1) |>
  ungroup() |>
  select(symbol, pe_ratio, forward_pe_ratio, peg_ratio, enterprise_value_ebitda_ratio)

# Combine fundamentals and valuation
screener_table <- latest_fundamentals |>
  left_join(latest_valuation, by = "symbol") |>
  mutate(
    # Screening criteria flags
    pass_margin    = operating_margin >= 0.25,
    pass_fcf       = fcf_conversion >= 0.70,
    pass_peg       = peg_ratio < 2.5,
    pass_valuation = forward_pe_ratio < 35,
    screen_passed  = pass_margin & pass_fcf & pass_peg & pass_valuation
  )

screener_table |>
  select(symbol, operating_margin, fcf_conversion, forward_pe_ratio, peg_ratio, screen_passed)

5. Visualizing Peer Comparisons

ggplot(fundamentals, aes(x = date, y = operating_margin, color = symbol)) +
  geom_line(linewidth = 1.1) +
  geom_point(size = 2.5) +
  scale_y_continuous(labels = percent_format(accuracy = 1)) +
  scale_x_date(date_labels = "%Y", date_breaks = "1 year") +
  labs(
    title    = "Annual Operating Margin Comparison",
    subtitle = "Operating income as a percentage of total revenue",
    x        = "Fiscal Year",
    y        = "Operating Margin",
    color    = "Company",
    caption  = "Source: Yahoo Finance via yahoofinancer"
  ) +
  theme_minimal(base_size = 12)

Valuation vs. Operating Profitability

ggplot(screener_table, aes(x = operating_margin, y = enterprise_value_ebitda_ratio, label = symbol)) +
  geom_point(aes(color = symbol), size = 4) +
  geom_text(vjust = -1, fontface = "bold") +
  scale_x_continuous(labels = percent_format(accuracy = 1)) +
  labs(
    title    = "EV / EBITDA Multiple vs. Operating Margin",
    subtitle = "Evaluating valuation premium relative to operating efficiency",
    x        = "Operating Margin",
    y        = "Enterprise Value / EBITDA Multiple",
    caption  = "Source: Yahoo Finance via yahoofinancer"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "none")

6. Minimal Reproducible Example

Below is the complete, self-contained fundamental screening script:

library(yahoofinancer)
library(dplyr)
library(ggplot2)
library(scales)

# 1. Define peer universe
tickers <- c("AAPL", "MSFT", "GOOG")

# 2. Fetch income statement and cash flow statements
income_df   <- yf_get_financials(tickers, statement_type = "income", frequency = "annual")
cashflow_df <- yf_get_financials(tickers, statement_type = "cash-flow", frequency = "annual")

# 3. Retrieve quarterly valuation measures
stocks       <- Tickers$new(tickers)
val_measures <- stocks$valuation_measures

# 4. Join and calculate key metrics
latest_income <- income_df |>
  group_by(symbol) |>
  slice_tail(n = 1) |>
  ungroup() |>
  mutate(operating_margin = operating_income / total_revenue) |>
  select(symbol, date, total_revenue, operating_margin)

latest_cf <- cashflow_df |>
  group_by(symbol) |>
  slice_tail(n = 1) |>
  ungroup() |>
  mutate(fcf_conversion = free_cash_flow / operating_cash_flow) |>
  select(symbol, fcf_conversion)

latest_val <- val_measures |>
  group_by(symbol) |>
  slice_tail(n = 1) |>
  ungroup() |>
  select(symbol, pe_ratio, forward_pe_ratio, peg_ratio, enterprise_value_ebitda_ratio)

screener_summary <- latest_income |>
  left_join(latest_cf, by = "symbol") |>
  left_join(latest_val, by = "symbol")

# 5. Display screener table
print(screener_summary)

7. Summary

In this guide, you learned how to:

  1. Retrieve Structured Statements: Access income, balance sheet, and cash flow data with yf_get_financials().
  2. Compute Standardized Metrics: Derive operating margins, net margins, and free cash flow conversion.
  3. Extract Valuation Multiples: Query multi-stock valuation ratios with Tickers$valuation_measures.
  4. Screen & Compare Stocks: Build custom screening rules combining profitability and market valuation.

8. Going Further

  • Quarterly Trend Analysis: Switch to frequency = "quarterly" to spot inflection points in revenue and margin trends earlier in the fiscal year.
  • Combine with Technicals: Pair fundamental screening with technical indicator snapshots via Ticker$technical_insights.
  • Historical Price Context: Use yf_download_prices() to chart stock price performance against earnings and fundamental milestones.
  • More recipes: See vignette("cookbook", package = "yahoofinancer") for 15 additional recipes spanning portfolios, currency conversion, and technical analysis.