SusFin_Assignment 3_BGao

Author

Boyuan Gao

Getting Started

#load the tidyverse 
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.4.4     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# this will stop R from presenting data in scientific notation, which can be annoying. 
options(scipen = 999)

blackrock_etf_screener <- read_csv("https://raw.githubusercontent.com/t-emery/sais-susfin_data/main/datasets/ishares_etf_screener_as_of_2023-12-27.csv") 
Rows: 424 Columns: 18
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (10): ticker, name, asset_class, sub_asset_class, region, market, locat...
dbl   (6): gross_expense_ratio_percent, net_expense_ratio_percent, net_asset...
dttm  (2): incept_date, net_assets_as_of

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Problem 1

blackrock_etf_screener_esg_funds <- blackrock_etf_screener |> 
  filter(!sustainable_classification == "NA") |> 
  group_by(sustainable_classification) |> 
  summarize(
    largest_fund_name = name[which.max(net_assets_usd)],
    n = n(),
    largest_fund_assets = max(net_assets_usd),
    total_class_assets = sum(net_assets_usd),
  ) |> 
  mutate(
    pct_largest_fund_class = (largest_fund_assets / total_class_assets) * 100,
    .after = largest_fund_name
  ) |> 
  arrange(desc(pct_largest_fund_class))

blackrock_etf_screener_esg_funds
# A tibble: 4 × 6
  sustainable_classification largest_fund_name      pct_largest_fund_class     n
  <chr>                      <chr>                                   <dbl> <int>
1 Impact                     iShares USD Green Bon…                  100       1
2 Thematic                   iShares Global Clean …                   89.2     6
3 Screened                   iShares ESG Screened …                   47.6     3
4 Uplift                     iShares ESG Aware MSC…                   26.0    30
# ℹ 2 more variables: largest_fund_assets <dbl>, total_class_assets <dbl>

Problem 2

smallest_etfs <- blackrock_etf_screener |>
  mutate(net_assets_mn_usd = net_assets_usd/10^6) |> 
  arrange(net_assets_mn_usd) |>
  slice_min(net_assets_mn_usd, n = 10)

smallest_etfs
# A tibble: 10 × 19
   ticker name                        incept_date         gross_expense_ratio_…¹
   <chr>  <chr>                       <dttm>                               <dbl>
 1 ERUS   iShares MSCI Russia ETF     2010-11-09 00:00:00                   0.65
 2 AGIH   iShares Inflation Hedged U… 2022-06-22 00:00:00                   0.16
 3 HYGI   iShares Inflation Hedged H… 2022-06-22 00:00:00                   1.01
 4 IRTR   iShares® LifePath® Retirem… 2023-10-17 00:00:00                   0.08
 5 ITDA   iShares® LifePath® Target … 2023-10-17 00:00:00                   0.09
 6 ITDH   iShares® LifePath® Target … 2023-10-17 00:00:00                   0.11
 7 ITDI   iShares® LifePath® Target … 2023-10-17 00:00:00                   0.11
 8 ITDG   iShares® LifePath® Target … 2023-10-17 00:00:00                   0.11
 9 ITDF   iShares® LifePath® Target … 2023-10-17 00:00:00                   0.11
10 IWFH   iShares Virtual Work and L… 2020-09-29 00:00:00                   0.47
# ℹ abbreviated name: ¹​gross_expense_ratio_percent
# ℹ 15 more variables: net_expense_ratio_percent <dbl>, net_assets_usd <dbl>,
#   net_assets_as_of <dttm>, asset_class <chr>, sub_asset_class <chr>,
#   region <chr>, market <chr>, location <chr>, investment_style <chr>,
#   msci_esg_fund_rating_aaa_ccc <chr>, msci_esg_quality_score_0_10 <dbl>,
#   msci_weighted_average_carbon_intensity_tons_co2e_m_sales <dbl>,
#   msci_esg_percent_coverage <dbl>, sustainable_classification <chr>, …
ggplot(smallest_etfs, 
       aes(x = fct_reorder(name, net_assets_mn_usd), 
           y = net_assets_mn_usd,)) +
  geom_bar(stat = "identity") +
  labs(title = "10 Smallest ETFs in BlackRock ETF",
       x = "ETF Name",
       y = "Net Assets (USD mn)",
       caption = "Boyuan Gao") +
  theme_minimal() +
  coord_flip()

Problem 3

glimpse(blackrock_etf_screener)
Rows: 424
Columns: 18
$ ticker                                                   <chr> "IVV", "IEFA"…
$ name                                                     <chr> "iShares Core…
$ incept_date                                              <dttm> 2000-05-15, …
$ gross_expense_ratio_percent                              <dbl> 0.03, 0.07, 0…
$ net_expense_ratio_percent                                <dbl> 0.03, 0.07, 0…
$ net_assets_usd                                           <dbl> 399312542067,…
$ net_assets_as_of                                         <dttm> 2023-12-27, …
$ asset_class                                              <chr> "Equity", "Eq…
$ sub_asset_class                                          <chr> "Large Cap", …
$ region                                                   <chr> "North Americ…
$ market                                                   <chr> "Developed", …
$ location                                                 <chr> "United State…
$ investment_style                                         <chr> "Index", "Ind…
$ msci_esg_fund_rating_aaa_ccc                             <chr> "A", "AA", "A…
$ msci_esg_quality_score_0_10                              <dbl> 6.6845, 7.519…
$ msci_weighted_average_carbon_intensity_tons_co2e_m_sales <dbl> 106.80, 102.8…
$ msci_esg_percent_coverage                                <dbl> 99.05, 99.80,…
$ sustainable_classification                               <chr> NA, NA, NA, N…
bottom_quantile <- quantile(blackrock_etf_screener$msci_esg_quality_score_0_10, .25, na.rm = TRUE)

bottom_quartile_funds <- blackrock_etf_screener |> 
  filter(msci_esg_quality_score_0_10 <= bottom_quantile)

bottom_quartile_funds
# A tibble: 104 × 18
   ticker name                        incept_date         gross_expense_ratio_…¹
   <chr>  <chr>                       <dttm>                               <dbl>
 1 IJR    iShares Core S&P Small-Cap… 2000-05-22 00:00:00                   0.06
 2 IEMG   iShares Core MSCI Emerging… 2012-10-18 00:00:00                   0.09
 3 IWM    iShares Russell 2000 ETF    2000-05-22 00:00:00                   0.19
 4 TLT    iShares 20+ Year Treasury … 2002-07-22 00:00:00                   0.15
 5 IEF    iShares 7-10 Year Treasury… 2002-07-22 00:00:00                   0.15
 6 SHY    iShares 1-3 Year Treasury … 2002-07-22 00:00:00                   0.15
 7 GOVT   iShares U.S. Treasury Bond… 2012-02-14 00:00:00                   0.05
 8 HYG    iShares iBoxx $ High Yield… 2007-04-04 00:00:00                   0.49
 9 TIP    iShares TIPS Bond ETF       2003-12-04 00:00:00                   0.19
10 SHV    iShares Short Treasury Bon… 2007-01-05 00:00:00                   0.16
# ℹ 94 more rows
# ℹ abbreviated name: ¹​gross_expense_ratio_percent
# ℹ 14 more variables: net_expense_ratio_percent <dbl>, net_assets_usd <dbl>,
#   net_assets_as_of <dttm>, asset_class <chr>, sub_asset_class <chr>,
#   region <chr>, market <chr>, location <chr>, investment_style <chr>,
#   msci_esg_fund_rating_aaa_ccc <chr>, msci_esg_quality_score_0_10 <dbl>,
#   msci_weighted_average_carbon_intensity_tons_co2e_m_sales <dbl>, …
bottom_quartile_funds |> 
  group_by(asset_class, sub_asset_class) |> 
  mutate(net_assets_bn_usd = net_assets_usd/10^9) |> 
  summarize(
    total_assets = sum(net_assets_bn_usd, na.rm = TRUE),
    number_of_funds = n(),
  )
`summarise()` has grouped output by 'asset_class'. You can override using the
`.groups` argument.
# A tibble: 10 × 4
# Groups:   asset_class [3]
   asset_class  sub_asset_class        total_assets number_of_funds
   <chr>        <chr>                         <dbl>           <int>
 1 Equity       All Cap                     95.5                 23
 2 Equity       Large Cap                    1.85                 1
 3 Equity       Large/Mid Cap               42.4                 10
 4 Equity       Small Cap                  188.                  18
 5 Fixed Income Corporates                   0.0413               1
 6 Fixed Income Credit                       1.99                 2
 7 Fixed Income Government                 221.                  24
 8 Fixed Income High Yield                  39.8                 17
 9 Fixed Income Inflation                   27.2                  4
10 Real Estate  Real Estate Securities       7.15                 4

Problem 4

  1. MSCI ESG Fund Rating: Developed Market vs. Emerging Market

    This chart ilustrates the distribution of MSCI ESG fund rating across all ETFs in both developed and emerging market. For ‘AA’ and ‘A’ ratings, there is a significantly higher count of funds from Developed Markets compared to Emerging Markets.

blackrock_etf_screener %>%
 filter(!is.na(msci_esg_fund_rating_aaa_ccc)) %>%
 ggplot() +
  aes(x = msci_esg_fund_rating_aaa_ccc, fill = market) +
  geom_bar() +
  scale_fill_manual(
    values = c(Developed = "#440154",
    Emerging = "#22908B",
    Frontier = "#FDE725")
  ) +
  labs(
    x = "MSCI ESG Fund Rating",
    y = "Number of Funds",
    title = "MSCI ESG Fund Rating: Developed Market vs. Emerging Market",
    caption = "Boyuan Gao"
  ) +
  coord_flip() +
  theme_minimal()

  1. ETF Formation (by Markets)

    This chart shows the ETF formation history by markets.

blackrock_etf_screener_w_new_features <- blackrock_etf_screener |> 
  mutate(
    # if the sustainable_classification column is NA, then the fund is not an ESG fund.
    standard_or_esg = if_else(
      condition = is.na(sustainable_classification),
      true = "Standard",
      false = "ESG"
    ),
    # lubridate::year() extracts the year from a date
    inception_year = lubridate::year(incept_date),
    # Change to a meaningful magnitude for the data. In asset management, billions is a good default. 
    net_assets_bn_usd = net_assets_usd/10^9,
  )

ggplot(blackrock_etf_screener_w_new_features) +
  aes(x = inception_year, fill = market) +
  geom_histogram(bins = 30L) +
  scale_fill_viridis_d(option = "viridis", direction = 1) +
  labs(
    x = "Inception Year",
    y = "Count",
    title = "ETF Formation by Markets",
    caption = "Boyuan Gao"
  ) +
  theme_minimal()

  1. MSCI ESG Score and Carbon Intensity

    This chart explores the relationship between the carbon intensity of ETFs and their ESG quality scores. From the chart, we can see there is a weak negative relationship between the two variables, meaning that funds with lower carbon intensity tend to have higher ESG scores.

    ggplot(blackrock_etf_screener_w_new_features) +
      aes(
        x = msci_esg_quality_score_0_10,
        y = msci_weighted_average_carbon_intensity_tons_co2e_m_sales,
        colour = standard_or_esg
      ) +
      geom_point(shape = "circle", size = 2L) +
      geom_smooth(span = 1L) +
      scale_color_hue(direction = 1) +
      scale_y_continuous(trans = "log10") +
      labs(
        x = "MSCI ESG Quality Score",
        y = "MSCI Weighted Average Color Intensity",
        title = "MSCI ESG Score and Carbon Intensity",
        subtitle = "ESG vs. Standard",
        caption = "Boyuan Gao"
      ) +
      theme_minimal()
    `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
    Warning: Removed 70 rows containing non-finite values (`stat_smooth()`).
    Warning: Removed 70 rows containing missing values (`geom_point()`).

  2. MSCI ESG Score and MSCI ESG Percentage Coverage

    This chart indicates the relationship between the MSCI ESG score of the fund and the MSCI ESG percent coverage. There is a weak positive relationship between the two variables, meaning that funds with higher MSCI ESG percentage coverage tend to have higher ESG scores.

blackrock_etf_screener_w_new_features %>%
 filter(net_assets_usd >= 0 & net_assets_usd <= 160000000000) %>%
 ggplot() +
  aes(
    x = msci_esg_quality_score_0_10,
    y = msci_esg_percent_coverage,
    colour = standard_or_esg
  ) +
  geom_point(shape = "circle", size = 1.5) +
  geom_smooth(span = 1L) +
  scale_color_viridis_d(option = "viridis", direction = -1) +
  scale_y_continuous(trans = "log10") +
  labs(
    x = "MSCI ESG Quality Score",
    y = "MSCI ESG Percentage Coverage",
    title = "MSCI ESG Score and MSCI ESG Percentage Coverage",
    subtitle = "ESG vs. Standard",
    caption = "Boyuan Gao"
  ) +
  theme_minimal()
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Warning: Removed 44 rows containing non-finite values (`stat_smooth()`).
Warning: Removed 44 rows containing missing values (`geom_point()`).

  1. MSCI ESG Score Distribution by Region

    This chart shows the MSCI ESG score distribution by region. It’s approximating to a bell shaped distribution.

blackrock_etf_screener_w_new_features %>%
 filter(net_assets_bn_usd >= 0L & net_assets_bn_usd <= 200L) %>%
 ggplot() +
  aes(x = msci_esg_quality_score_0_10, fill = region) +
  geom_histogram(bins = 30L) +
  scale_fill_viridis_d(option = "viridis", direction = -1) +
  labs(
    x = "MSCI ESG Quality Score",
    y = "Count",
    title = "MSCI ESG Score Distribution by Region",
    caption = "Boyuan Gao"
  ) +
  theme_minimal()
Warning: Removed 44 rows containing non-finite values (`stat_bin()`).