The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.

Fundamental Analysis and Stock Screening

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:

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

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

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.