O objetivo aqui é tentar reproduzir os resultados deste post (do blog o pequeno investidor) que avalia alguns indicadores da Petrobrás no cenário atual, começo de 2014, utilizando Python e alguns pacotes do stack científico.

Vamos começar importando os dados e para isso vamos utilizar o pandas. pandas possui uma classe denominada DataFrame que pode carregar dados em formatos híbridos em uma estrutura tabular. Isso é muito legal porque em muitos casos os dados não estão em formatos numéricos e frequentemente é necessário, mesmo em dados numéricos, que eles sejam categorizados e ao invés de utilizar códigos sem sentido podemos lançar mão de identificadores de texto. Mas felizmente os dados que vamos começar a análisar são numéricos e representam o a Demostração de Resultados da Petrobrás. Estes dados foram obtidos do site Fundamentus que organiza as informações históricas de Balanço e Demostração de Resultados para diversas empresas, de graça!

In [1]:
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import sys
%pylab inline
print 'python', sys.version
print 'pandas', pd.__version__
Populating the interactive namespace from numpy and matplotlib
python 2.7.6 (default, Nov 28 2013, 23:33:48) 
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)]
pandas 0.13.0

Importando os dados

Vamos começar a análise observando os dados de resultados operacionais da Petrobrás: Receita Líquida e Lucro Líquido. Estas variáveis se encontram no Demonstrativo de Resultados da empresa, e estão no arquivo PETR balanco - T Dem. Result.csv. Vamos começar dando uma olhada nesse arquivo.

In [2]:
!head -3 'PETR balanco - T Dem. Result.csv'
Data,Receita Bruta de Vendas e/ou Serviços,Deduções da Receita Bruta,Receita Líquida de Vendas e/ou Serviços,Custo de Bens e/ou Serviços Vendidos,Resultado Bruto,Despesas Com Vendas,Despesas Gerais e Administrativas,Perdas pela Não Recuperabilidade de Ativos ,Outras Receitas Operacionais,Outras Despesas Operacionais,Resultado da Equivalência Patrimonial,Financeiras,Receitas Financeiras,Despesas Financeiras,Resultado Não Operacional,Receitas,Despesas,Resultado Antes Tributação/Participações,Provisão para IR e Contribuição Social,IR Diferido,Participações/Contribuições Estatutárias,Reversão dos Juros sobre Capital Próprio,Part. de Acionistas Não Controladores,Lucro/Prejuízo do Período
30/09/2013,0,0,77700317.18,-61114642.43,16585674.75,-2862032.896,-2802718.976,0,0,-5426829.824,493439.008,-1020598.976,1205330.944,-2225929.984,0,0,0,4966932.992,590804.992,-2015576.96,0,0,-147488.992,3394672.128
30/06/2013,0,0,73626247.17,-54919024.64,18707228.67,-2552424.96,-2589590.016,0,0,-2458756.096,389620,-3550694.912,909123.968,-4459819.008,0,0,0,7945380.864,793974.976,-3060443.904,0,0,522078.016,6200990.208

Bem, já dá pra ver que os nomes das colunas não são religiosos o que já implica no esforço de renomear algumas colunas para tornar a manipulação mais simples. Outra coisa é o formato de data, que no Brasil é dia/mês/ano. Para o pandas isso não é um problema como veremos, com algumas opções vamos importar o arquivo sem maiores problemas.

In [3]:
dem_result = pd.read_csv('PETR balanco - T Dem. Result.csv', parse_dates=[0], dayfirst=True)
print 'shape:', dem_result.shape
print 'columns:'
print dem_result.dtypes
shape: (32, 25)
columns:
Data                                           datetime64[ns]
Receita Bruta de Vendas e/ou Serviços                 float64
Deduções da Receita Bruta                             float64
Receita Líquida de Vendas e/ou Serviços               float64
Custo de Bens e/ou Serviços Vendidos                  float64
Resultado Bruto                                       float64
Despesas Com Vendas                                   float64
Despesas Gerais e Administrativas                     float64
Perdas pela Não Recuperabilidade de Ativos              int64
Outras Receitas Operacionais                            int64
Outras Despesas Operacionais                          float64
Resultado da Equivalência Patrimonial                 float64
Financeiras                                           float64
Receitas Financeiras                                  float64
Despesas Financeiras                                  float64
Resultado Não Operacional                             float64
Receitas                                              float64
Despesas                                              float64
Resultado Antes Tributação/Participações              float64
Provisão para IR e Contribuição Social                float64
IR Diferido                                           float64
Participações/Contribuições Estatutárias              float64
Reversão dos Juros sobre Capital Próprio                int64
Part. de Acionistas Não Controladores                 float64
Lucro/Prejuízo do Período                             float64
dtype: object

Como podemos observar o DataFrame dem_result possui 32 linhas e 25 colunas, das quais: 1 datetime64, 21 float64, e 3 int64. Os campos numéricos foram identificados na carga do DataFrame, já o campo data foi identificado utilizando as opções parse_dates=[0] e dayfirst=True indicando que a primeira coluna possui uma data e que na data o primeiro campo era o dia.

In [4]:
dem_result.Data.head()
Out[4]:
0   2013-09-30 00:00:00
1   2013-06-30 00:00:00
2   2013-03-31 00:00:00
3   2012-12-31 00:00:00
4   2012-09-30 00:00:00
Name: Data, dtype: datetime64[ns]

O interessante é que a função read_csv já resolve uma parte importante do meu problema que é a transformação dos dados. Para o que eu pretendo fazer com este dataset todas as colunas já estão com os tipos corretamente definidos.

Arrumando os nomes das colunas

Agora vamos atacar as colunas! Algumas colunas estão com os nomes zoados, com espaços, acentos e outros símbolos que dificultam a utilização do DataFrame, por isso vamos renomear as colunas que desejamos utilizar. O DataFrame possui um método rename que torna essa tarefa fácil-fácil.

In [5]:
dem_result = dem_result.rename(columns={'Receita Líquida de Vendas e/ou Serviços':'ReceitaLiquida',
                           'Lucro/Prejuízo do Período':'LucroLiquido'})

Do ponto de vista do acionista a Petrobrás está estagnada

O artigo argumenta que do ponto de vista do acionista a empresa está estagnada desde 2004. Bem, para tentarmos observar isso vamos criar outro dataset com as colunas Data, ReceitaLiquida, e LucroLiquido, que são variáveis objeto da nossa análise.

In [6]:
oper_result = dem_result.loc[:,['Data', 'ReceitaLiquida', 'LucroLiquido']]
oper_result.head()
Out[6]:
Data ReceitaLiquida LucroLiquido
0 2013-09-30 00:00:00 77700317.18 3394672.128
1 2013-06-30 00:00:00 73626247.17 6200990.208
2 2013-03-31 00:00:00 72535343.10 7693176.832
3 2012-12-31 00:00:00 73405251.58 7747591.168
4 2012-09-30 00:00:00 73792937.98 5566355.968

5 rows × 3 columns

Vamos agora visualizar a evolução dessas grandezas ao longo dos anos e entender um pouco melhor a dinâmica delas.

In [7]:
import datetime
import matplotlib.dates as mdates
from matplotlib.ticker import FuncFormatter

years    = mdates.YearLocator()   # every year
months   = mdates.MonthLocator()  # every month
yearsFmt = mdates.DateFormatter('%Y')

fig, ax = plt.subplots(figsize=(10,6))

xx = [datetime.date(d.year, d.month, d.day) for d in oper_result.Data]
rects1 = ax.plot(xx, oper_result.LucroLiquido, 'go-')
rects2 = ax.plot(xx, oper_result.ReceitaLiquida, 'bo-')

ax.xaxis.set_major_locator(years)
ax.xaxis.set_major_formatter(yearsFmt)
ax.xaxis.set_minor_locator(months)

datemin = datetime.date(oper_result.Data.min().year, 1, 1)
datemax = datetime.date(oper_result.Data.max().year+1, 1, 1)
ax.set_xlim(datemin, datemax)

formatter = FuncFormatter(lambda x, pos: '$%1.1fB' % (x*1e-6))
ax.yaxis.set_major_formatter(formatter)

ax.set_title('Receita e Lucro trimestrais')
ax.legend( (rects1[0], rects2[0]), ('LucroLiquido', 'ReceitaLiquida'), loc='center right')
ax.grid(True)

O gráfico acima, que by the way me deu muito trabalho para criar exatamente da forma que eu queria (talvez no futuro eu escreva um post sobre isso), apresenta o resultado operacional da Petrobrás. Uma comparação da sua receita líquida, ao longo de aproximadamente 8 anos, contra o seu lucro líquido. É bastante claro que há uma diferença significativa entre o Lucro e a Receita. Esta última cresceu significativamente nos últimos anos, por outro lado o Lucro não acompanhou essa toada.

No entanto, eu gostaria de ver estes números consolidados por ano e avaliar esta evolução em um gráfico de barras. Para isso vamos criar uma coluna Ano no DataFrame e fazer o agrupamento por ela.

In [8]:
oper_result['Ano'] = [d.year for d in oper_result.Data]
anual_result = oper_result.groupby(['Ano'])[['ReceitaLiquida', 'LucroLiquido']].sum()

fig, ax = plt.subplots(nrows=2, ncols=1, figsize=(10,10))

rects1 = ax[0].plot(anual_result.index, anual_result.LucroLiquido, 'go-')
rects2 = ax[0].plot(anual_result.index, anual_result.ReceitaLiquida, 'bo-')
ax[0].set_xticks(anual_result.index + 0.35)
ax[0].set_xticklabels( anual_result.index )
ax[0].grid(True)
ax[0].set_title('Receita e Lucro anuais')
ax[0].legend( (rects1[0], rects2[0]), ('LucroLiquido', 'ReceitaLiquida'), loc='upper left')
ax[0].yaxis.set_major_formatter(formatter)

rects1 = ax[1].bar(anual_result.index, anual_result.LucroLiquido, 0.35, color='g')
rects2 = ax[1].bar(anual_result.index+0.35, anual_result.ReceitaLiquida, 0.35, color='b')
ax[1].set_title('Receita e Lucro anuais')
ax[1].set_xticks(anual_result.index + 0.35)
ax[1].set_xticklabels( anual_result.index )
ax[1].legend( (rects1[0], rects2[0]), ('LucroLiquido', 'ReceitaLiquida'), loc='upper left')
ax[1].grid(True)
ax[1].yaxis.set_major_formatter(formatter)

Tarefa relativamente fácil com pandas, agrupar o DataFrame por uma coluna e definir qual função deve ser utilizada no agrupamento. No entanto, para mim seria mais intuitivo utilizar uma abordagem mais funcional para o agrupamento. Poderiamos ter algo como

oper_ressult.groupby(['Ano'], ['ReceitaLiquida', 'LucroLiquido'], sum)

Eventualmente poderiamos definir transformações diferentes para o agrupamento de diferentes colunas. De qualquer forma, o problema está resolvido e foi relativamente fácil.

O gráfico em barras acima é melhor do que o gráfico em linhas quando queremos tornar mais evidente que as quantidades, Lucro e Receita, são bastante diferentes, no que se refere a massa. É notório que o Lucro é muito menor do que a receita. O gráfico em barras traz a evolução temporal e enfatiza a ordem de grandeza das duas variáveis. O gráfico de linha dá mais foco a dinâmica e pode, eventualmente, negligenciar a ordem de grandeza, por outro lado torna mais evidente tendências nas variáveis.

Bem, essas variáveis não são difíceis de entender. Receita é o que a empresa fatura e lucro é o que ela põe no bolso. Não tá muito difícil ver que a distância entre o que a Petrobrás ganha e o que ela põe no bolso vem aumentando significativamente e não tem mágica, esse dinheiro tem que ir pra algum lugar. Então em termos de lucro a Petrobrás realmente deu uma estagnada nos últimos anos.

Mas apenas o lucro e a receita não definem essa questão. Outra variável importante e complementar ao que estamos observando é o lucro pro ação. Para calcular o lucro por ação basta dividir o lucro líquido pela quantidade total de ações da empresa. Essa informação pode ser obtida no site da BM&F Bovespa, em 2013 a Petrobrás tinhas 6.743.719.951 ações (somando Preferenciais e Ordinárias).

In [9]:
anual_result.Qtd = 6743719951
anual_result.LPA = anual_result.LucroLiquido*1000/anual_result.Qtd
fig, ax = plt.subplots(figsize=(10,6))

ax.plot(anual_result.index, anual_result.LPA, 'go-')
ax.set_xticks(anual_result.index)
ax.set_xticklabels(anual_result.index)
ax.grid(True)
ax.set_title('LPA')
Out[9]:
<matplotlib.text.Text at 0x114efb650>

LPA significa lucro por ação e indica, em R$, quanto a empresa paga ao acionista por deter a sua ação. Como opequenoinvestidor menciona, o número por si não diz muita coisa, no entanto, se este número é crescente ao longo do tempo e a quantidade de ações da empresa permanece constante, significa que a empresa está lucrando e que sua ação está sofrendo os bons reflexos da sua performance e portanto o acionista está sendo recompensado.

No entanto, a Petrobrás aumentou o número de ações em 2010, ou seja, a empresa fez uma captação. Por isso o aumento da receita.

O gráfico acima não reflete os resultados apresentados no post do opequenoinvestidor e nem os números apresentados no site do Bastter. Acredito que isto aconteca porque não tenho a série da quantidade de ações por ano. No site da BM&F Bovespa tem apenas a quantidade de ações em 29/04/2013. Dado que houve a capitalização em 2010 a quantidade de ações antes de 2010 deve ser menor da que eu utilizei. Devemos então considerar esta série de LPA como uma proxy.

Entretanto é possível observar que o LPA vem decrescendo desde 2010, reflexo da captalização e da ausência de lucros expressivos, para compensar este aumento.

Adicionando a série de preços da Petrobrás

Queria ainda de adicionar a esta análise o valor da ação da Petrobrás. Para capturar a série temporal da ação da Petrobrás vamos utilizar o Quandl, que é um excelente serviço de datasets online.

In [10]:
import Quandl
petr4 = Quandl.get("GOOG/BVMF_PETR4", authtoken='nJ1NhTYdEs2p3MsS4CVd')
petr4 = pd.DataFrame({'Fechamento' : list(petr4['Close']), 'Data' : list(petr4.index)}, index=np.arange(len(petr4)))
petr4.head()
Token nJ1NhTYdEs2p3MsS4CVd activated and saved for later use.
Returning Dataframe for  GOOG/BVMF_PETR4
Out[10]:
Data Fechamento
0 2003-06-02 00:00:00 6.56
1 2003-06-03 00:00:00 6.55
2 2003-06-04 00:00:00 6.75
3 2003-06-05 00:00:00 6.74
4 2003-06-06 00:00:00 6.79

5 rows × 2 columns

Capturamos a série temporal das ações preferenciais da Petrobrás (PETR4) e para efeito de comparação vamos pegar o último ponto de cada ano para formar uma série temporal anual e comparar com os dados de receita e lucro.

In [11]:
petr4['Ano'] = [d.year for d in petr4.Data]
g = petr4.groupby(['Ano'])
anual_petr4 = g.apply(lambda x: x.sort('Data').loc[:,['Ano', 'Fechamento']].tail(1))
anual_petr4.index = anual_petr4.Ano
anual_petr4 = anual_petr4.loc[:,'Fechamento']
anual_petr4.tail()
Out[11]:
Ano
2010    27.29
2011    21.49
2012    19.52
2013    17.08
2014    15.10
Name: Fechamento, dtype: float64

Tendo a série anual, temos agora que fazer um join dos DataFrame.

In [12]:
anual_result_2 = anual_result.join(anual_petr4, how='outer')
anual_result_2
Out[12]:
ReceitaLiquida LucroLiquido Fechamento
Ano
2003 NaN NaN 9.55
2004 NaN NaN 12.14
2005 3.961811e+07 8141880.320 18.60
2006 1.582388e+08 25918920.704 24.90
2007 1.705777e+08 21511788.800 44.20
2008 2.151185e+08 32987791.358 22.84
2009 1.827101e+08 28981708.800 36.69
2010 2.132736e+08 35189366.276 27.29
2011 2.441761e+08 33313094.662 21.49
2012 2.813795e+08 21182441.472 19.52
2013 2.238619e+08 17288839.168 17.08
2014 NaN NaN 15.10

12 rows × 3 columns

O join é legal porque ele atua sobre o index das estruturas de dado, e ai pode ser DataFrame ou Series, mas apenas DataFrame possui o método join. Portanto, Podemos apenas fazer o join entre DataFrame e de uma Series em um DataFrame, como vimos acima.

Utilizei o parâmetro how=outer para obter a maior abrangência dos índices e consequentemente as colunas ReceitaLiquida e LucroLiquido vieram com valores NaN, pois não possuem dados referentes aos anos de 2003, 2004 e 2014.

In [13]:
fig, ax = plt.subplots(nrows=2, ncols=1, figsize=(10,10), dpi=300)

rects1 = ax[0].bar(anual_result_2.index, anual_result_2.LucroLiquido, 0.35, color='g')
rects2 = ax[0].bar(anual_result_2.index+0.35, anual_result_2.ReceitaLiquida, 0.35, color='b')
ax[0].set_title('Receita e Lucro anuais')
ax[0].set_xticks(anual_result_2.index + 0.35)
ax[0].set_xticklabels(anual_result_2.index)
ax[0].legend( (rects1[0], rects2[0]), ('LucroLiquido', 'ReceitaLiquida'), loc='upper left')
ax[0].grid(True)

ax[1].plot(anual_result_2.index, anual_result_2.Fechamento, 'o-')
ax[1].set_xticks(anual_result_2.index)
ax[1].set_xlim((2003,2014))
ax[1].set_title('PETR4')
ax[1].grid(True)

É importante notar que as colunas com NaN não aparecem no gráfico, ou seja, eu possuo um registro de índice mas não possuo ponto para o registro. No gráfico em barras não fica estranho, no entanto, no gráfico em linha ficariam segmentos soltos no meio do gráfico.

Conclusão

Por ora, podemos observar com os dados:

  • a discrepância entre receita e lucro
  • a queda do lucro por ação

Ainda coloquei o gráfico de preços das ações junto dos resultados operacionais para avaliar como a variação nos preços estaria relacionada com estas variáveis. É possível observar que assim como o LPA o valor da ação vem diminuindo desde 2010, o que não é um bom indicador para os acionistas.

Vamos ficando por aqui mas a análise não terminou. Ainda temos outras variáveis para e o que foi colocado aqui é apenas o começinho do post.

Na verdade o grosso do trabalho foi de importação, formatação e compreensão dos dados e como utilizar o pandas para tudo isso. Isso consome tempo mesmo. Nos próximos textos vamos direto ao ponto já trabalhando nas variáveis e esmiuçando os indicadores da empresa utilizando Python.