Balance Sheet Analysis for Investment Decision Support
Finance
R
Author
Sidney Bissoli
Published
January 5, 2025
Overview
This project demonstrates how to extract annual balance sheets from publicly traded companies listed on B3 (Brazilian stock exchange), and to calculate liquidity ratios (current, quick, immediate, and general) from this data, in order to inform investment decisions.
Methodology
Using R programming and financial data analysis techniques, this study focuses on four key liquidity ratios:
Current Ratio: Current assets / Current liabilities
Quick Ratio: (Current assets - Inventory) / Current liabilities
Immediate Ratio: Cash and equivalents / Current liabilities
# install pacman if it is not installedif (!require("pacman")) install.packages("pacman")
Carregando pacotes exigidos: pacman
# install packages if they are not installed and load thempacman::p_load( tidyverse, GetDFPData2, janitor, here, scales, ggthemes, plotly, DT, crosstalk, htmltools )
# store company namecia_name <-"anima"# get datadata_info <-get_info_companies()# subset companies currently traded on B3info_cleaned <- data_info |>clean_names() |>rename(name_social = denom_social,name_commercial = denom_comerc,registro = sit_reg,code_cvm = cd_cvm,sector = setor_ativ,tipo = tp_merc ) |>mutate(across(c(registro, tipo, sector), str_to_lower)) |>filter(registro =="ativo") |>filter(tipo =="bolsa") |>mutate(sector =str_remove_all(sector, ".*- "))# store company sectorcia_sector <- info_cleaned |>filter(str_detect(name_social, str_to_upper(cia_name))) |>pull(sector)# store company codes from the same economic sector of company chosencvm_codes <- info_cleaned |>filter(sector == cia_sector) |>pull(code_cvm)# download balance sheet datalist_bp <-get_dfp_data(companies_cvm_codes = cvm_codes,type_docs =c("BPA","BPP"),type_format ="con",clean_data =TRUE,use_memoise =FALSE,cache_folder ="gdfpd2_cache")
Data Processing and Liquidity Calculations
# convert list into data set and provide basic manipulationsdata_bp <- list_bp |>bind_rows() |>clean_names() |>rename(cnpj = cnpj_cia, date = dt_refer,name = denom_cia,escala = escala_moeda,value = vl_conta,index = cd_conta,description = ds_conta ) |>mutate(across(c(escala, description), str_to_lower)) |>mutate(value =case_when( escala =="mil"~ value *1000,TRUE~ value ))# store company complete namecia_name_complete <- data_bp |>filter(str_detect(name, str_to_upper(cia_name))) |>distinct(name) |>pull(name)# store variables of interestvariables_chosen <-c("ativo circulante","passivo circulante","caixa e equivalentes de caixa","ativo realizável a longo prazo","passivo não circulante")# store index for inventory (estoques)index_chosen <-"1.01.04"# convert data to wide formatdata_wide <- data_bp |>filter(description %in% variables_chosen | index %in% index_chosen) |>select(date, name, description, value) |>mutate(description =case_when(str_detect(description, "caixa") ~"caixa",str_detect(description, "realizável") ~"at_realizavel",str_detect(description, "não circulante") ~"pa_nao_circ",str_detect(description, "ativo circulante") ~"at_circ",str_detect(description, "passivo circulante") ~"pa_circ",TRUE~ description )) |>pivot_wider(names_from = description, values_from = value) |>group_by(date) |>mutate(n =n()) |>ungroup()# store abbreviated variablesvariables_abbreviated <-c("at_circ","pa_circ","caixa","at_realizavel","pa_nao_circ","estoques")# calculate sector totalsdata_total <- data_wide |>select(-name) |>group_by(date) |>mutate(across(any_of(variables_abbreviated), sum, na.rm =TRUE)) |>ungroup() |>mutate(name =str_to_upper("total"), .after = date) |>distinct()# combine company data with sector averagesdata_subset <- data_wide |>bind_rows(data_total) |>mutate(name =str_to_lower(name)) |>filter(str_detect(name, cia_name) | name =="total") |>mutate(name =str_to_upper(name))# calculate liquidity ratiosdata_multiplos <- data_subset |>mutate(liq_corrente = at_circ / pa_circ,liq_seca = (at_circ -coalesce(estoques, 0)) / pa_circ,liq_imediata = caixa / pa_circ,liq_geral = (at_circ + at_realizavel) / (pa_circ + pa_nao_circ) ) |>select(date, name, starts_with("liq"), n)
# order levels for classificationlevels <-c("adequate", "regular", "concerning", "critical" )# prepare data for visualization with classificationdata_classification <- data_multiplos |>group_by(date) |>filter(n() ==2) |>ungroup() |>arrange(date) |>pivot_longer(cols =starts_with("liq"), names_to ="variable") |>mutate(classification =case_when( variable =="liq_corrente"& value <1~"critical", variable =="liq_corrente"& value >=1& value <1.2~"concerning", variable =="liq_corrente"& value >=1.2& value <1.5~"regular", variable =="liq_corrente"& value >=1.5~"adequate", variable =="liq_seca"& value < .8~"critical", variable =="liq_seca"& value >= .8& value <=1~"concerning", variable =="liq_seca"& value >1~"adequate", variable =="liq_imediata"& value < .15~"critical", variable =="liq_imediata"& value >= .15& value <=.3~"concerning", variable =="liq_imediata"& value > .3~"adequate", variable =="liq_geral"& value <1~"critical", variable =="liq_geral"& value >=1& value <1.2~"concerning", variable =="liq_geral"& value >=1.2& value <1.5~"regular", variable =="liq_geral"& value >=1.5~"adequate", )) |>mutate(classification =str_to_title(classification)) |>mutate(classification =factor(classification, levels =str_to_title(levels))) |>mutate(year =year(date), .after = date)# nest data by liquidity typedata_nested <- data_classification |>nest(data =-variable)
Data Sources: B3 (Brazilian Stock Exchange) official financial reports Analysis Tools: R (tidyverse, GetDFPData2, ggplot2, plotly) Methodology: Balance sheet analysis with sector benchmarking Interactivity: Dynamic plots with hover information and classification tooltips
This analysis demonstrates the integration of financial data extraction, statistical analysis, and interactive visualization techniques using R programming. The complete methodology can be adapted for analyzing any publicly traded company on B3.