4.1. Análise dos dados de periódicos da Hemeroteca Digital Brasileira da Biblioteca Nacional#

Os dados foram disponibilizados para pesquisa pela equipe da BNDigital em formato xml. O arquivo foi exportado da base do sistema de periódicos digitalizados e se encontra no padrão MARCXML (MAchine-Readable Cataloging XML).

O arquivo pode ser acessado aqui.

Agradeço a Vinicius Pontes Martins do Setor de Gestão de Programas e Inovação (SGPI) e toda a equipe da BNDigital pela disponibilização dos dados.

Analisando e Filtrando o xml#

Aqui vamos realizar realizar o parse dos dados contidos no arquivo xml e filtrar os dados que serão utilizados para análise.

Utilizaremos a biblioteca xml.etree.ElementTree para realizar o parse do arquivo xml. A biblioteca é nativa do Python e não necessita de instalação.

# importar bibliotecas
import xml.etree.ElementTree as ET
# ler arquivo xml
tree = ET.parse('../../../data/bndbr/exp_per_marcxml.xml')

Após realizarmos o parse do ficheiro, podemos contar quantos registros exixtem na àrvore de elementos:

# contar número de registros
root = tree.getroot()
print(len(root))
7685

Subcampos utilizados#

Após a leitura do arquivos, selecionei sete subcampos para análise (serão listadas a tag e o subcampo de acordo com a estrutura do xml):

  • tag 245; subcampo a: Título do periódico

  • tag 245; subcampo b: Subtítulo do periódico

  • tag 260; subcampo a: Local de publicação

  • tag 260; subcampo b: Editora

  • tag 260; subcampo c: Período de publicação

  • tag 310; subcampo a: Periodicidade da publicação

  • tag 546; subcampo a: Idioma da publicação

A seleção desses elementos buscou possibilitar a comparação com os dados disponibilizados pela BND-PT, conforme descrito no tópico 4.1.

Vamos criar um ficheiro csv com os dados selecionados. Para isso, vamos utilizar a biblioteca csv do Python.

Primeiro criamos uma função para encontrar os subcampos desejados:

# função para encontrar o valor de um campo
def find_value(record, tag,code):
    return record.find(f"./datafield[@tag='{tag}']/subfield[@code='{code}']")

Em seguida, criamos o ficheiro csv e escrevemos os dados:

# criar um csv com os dados selecionados
import csv

with open('../../../data/complete_data.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.writer(f, delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL)
    writer.writerow(['title', 'subtitle', 'place', 'period', 'publisher', 'periodicity', 'language'])
    for record in root:
        title = find_value(record, '245', 'a')
        subtitle = find_value(record, '245', 'b')
        place = find_value(record, '260', 'a')
        period = find_value(record, '260', 'c')
        publisher = find_value(record, '260', 'b')
        periodicity = find_value(record, '310', 'a')
        language = find_value(record, '546', 'a')
        if title is not None:
            title = title.text
        if subtitle is not None:
            subtitle = subtitle.text
        if place is not None:
            place = place.text
        if period is not None:
            period = period.text
        if publisher is not None:
            publisher = publisher.text
        if periodicity is not None:
            periodicity = periodicity.text
        if language is not None:
            language = language.text
        writer.writerow([title, subtitle, place, period, publisher, periodicity, language])

Apresentação dos dados#

A partir do ficheiro csv gerado na célula anterior, vamos apresentar os dados da HDB buscando uma compreensão geral do seu acervo de periódicos digitalizados.

Os dados serão analisados com a biblioteca pandas e apresentados com a biblioteca plotly.

# importar bibliotecas
import pandas as pd
import plotly.express as px
import plotly.io as pio
import plotly.offline as py
pio.renderers.default = "notebook"

Dados gerais do acervo#

Vamos criar um dataframe com os dados do csv e apresentar sua estrutura e informações gerais.

# importar dataset e criar dataframe
df = pd.read_csv('../../../data/complete_data.csv', encoding='utf-8')

Para termos uma ideia geral do dataframe, vamos ver as primeiras 10 linhas do dataframe:

df.head()
title subtitle place period publisher periodicity language
0 O Abolicionista Paraense NaN Belém, PA 1883- Typ. da Provincia do Para Semanal por
1 O Abolicionista propriedade de uma associação São Luis, MA 1885 Typ. do Abolicionista NaN por
2 O Academico periodico scientifico, litterario e especialme... Rio de Janeiro, RJ 1855- Typ. Fluminense, de D.L. dos Santos NaN por
3 A Actualidade orgao do Partido Liberal Ouro Preto, MG 1878-[1882] Typ. de Jose Egydio da Silca Campos 3 vezes por semana NaN
4 A Actualidade periodico imparcial, litterario, critico e not... Maranhão 1900- Typ. de Antonio Pereira Ramos d'Almeida e C. S... 3 vezes por mês por

O dataframe é composto pelas seguintes colunas:

# mostrar colunas em lista
df.columns.tolist()
['title',
 'subtitle',
 'place',
 'period',
 'publisher',
 'periodicity',
 'language']
# contar número de registros
df.count()
title          7685
subtitle       3666
place          7608
period         7265
publisher      5675
periodicity    5901
language       6614
dtype: int64

Percebemos que o dataframe conta com 7685 periódicos. Os dados das demais colunas variam, e as colunas período e local de publicação são as mais completas.

Idiomas#

Vamos contar os idiomas dos periódicos digitalizados, mas primeiro vamos limpar os dados da coluna ‘language’.

# limpar dados de idioma
# lista de termos para substituir
replace_por = ['Texto em português', 'Texto em portugues', 'Português', 'Em português', 'Texto em português e alguns textos em francês']
replace_spa = ['Texto em espanhol', 'esp']
replace_fre = ['Texto em frances']

# substituir termos
df['language'] = df['language'].replace(replace_por, 'por')
df['language'] = df['language'].replace(replace_spa, 'spa')
df['language'] = df['language'].replace(replace_fre, 'fre')

Vamos avaliar quantos registros não possuem idioma definido:

# contar quantos registros não possuem idioma
df['language'].isnull().sum()
1071
# calcular a porcentagem de registros sem idioma
df['language'].isnull().sum() / len(df) * 100
13.936239427456082

Dos 7685 registros, 1071 não possuem idioma definido no dataframe, o que corresponde a 13,93% do total.

Vamos incluir o valor ‘Não definido’ para os registros sem idioma definido:

# incluir valor 'não definido' para registros sem idioma
df['language'] = df['language'].fillna('não definido')

Agora uma contagem dos idiomas dos periódicos será mais precisa:

# criar dataframe com a contagem de idiomas
df_lang = df['language'].value_counts().rename_axis('Idioma').reset_index(name='Quantidade')
df_lang
Idioma Quantidade
0 por 6462
1 não definido 1071
2 ger 61
3 ita 33
4 fre 27
5 spa 17
6 eng 10
7 ara 2
8 syr 1
9 yid 1

Em termos de porcentagem, podemos ver que o idioma predominante é o português, com 84% dos periódicos. Em seguida, temos os periódicos sem idioma definido, 13,9%, seguidos de alemão, italiano, francês, espanhol e inglês, cada um com menos de 1% cada.

# porcentagem de idiomas
df['language'].value_counts(normalize=True)
por             0.840859
não definido    0.139362
ger             0.007938
ita             0.004294
fre             0.003513
spa             0.002212
eng             0.001301
ara             0.000260
syr             0.000130
yid             0.000130
Name: language, dtype: float64
# criar função para gerar gráfico de barras
def bar_chart(df, x, y, title, color, range_x, range_y):
    fig = px.bar(df, x=x, y=y, title=title, color=color)
    fig.update_xaxes(tickangle=45, tickfont=dict(size=10))
    if range_x is not None:
        fig.update_xaxes(range=[range_x[0], range_x[1]])
    if range_y is not None:
        fig.update_yaxes(range=[range_y[0], range_y[1]])
    fig.update_layout(height=800, width=1200)
    return fig

Podemos visualizar de forma mais clara esses dados com um gráfico de barras:

# criar gráfico de barras com os dados de idiomas dos periódicos
fig1 = bar_chart(df_lang, 'Idioma', 'Quantidade', 'Idiomas dos periódicos', 'Idioma', None, None)
fig1

Período de publicação#

Vamos analisar os dados referentes aos períodos de publicação dos periódicos digitalizados. Esses dados possuem algumas características que tornam sua análise mais complexa.

Primeiro, percebemos que existe uma falta de padronização dos dados. Alguns registros possuem apenas o ano de início, outros possuem o ano de início e fim, e esses dados são escritos de formas variadas.

Portanto, primeiramente vou efetuar uma limpeza e padronização dos dados. Para isso, vou excluir caracteres especiais e buscar padronizar os dados para o formato ‘yyyy-yyyy’.

Assim, acredito minimizar os erros de análise e facilitar a visualização dos dados.

#  limpar dados do período de publicação usando regex
# excluir [, ], (, ), :
df['period'] = df['period'].replace(to_replace=r'[\[\]\(\):\?]', value='', regex=True)
#substituir ' - ' por '-'
df['period'] = df['period'].replace(to_replace=r' - ', value='-', regex=True)
#substituir ' a ' por '-'
df['period'] = df['period'].replace(to_replace=r' a ', value='-', regex=True)

Para tornar a análise e visualização mais eficiente, vou criar novas colunas para o ano de início e fim dos periódicos.

Caso o registro não possua o ano de fim, vou considerar o ano de início como ano de fim.

# criar coluna com o ano de início da publicação
# se iniciar com dígito, pegar os 4 primeiros caracteres, senão, pegar do 2º ao 5º caractere
df['start_year'] = df['period'].str.extract(r'(\d{4})', expand=False).fillna(df['period'].str[1:5])
# criar coluna com o ano de término da publicação a partir da coluna period
# pegar 4 últimos dígitos que aparecem na coluna period usando regex
df['end_year'] = df['period'].str.extract(r'(\d{4})$', expand=False)
# se 'end_year' for nulo, pegar o ano de início da publicação
df['end_year'] = df['end_year'].fillna(df['start_year'])

Uma última limpeza final, para excluir possíveis caracteres que não sejam dígitos nas colunas de ano de início e fim e inserior o número 0 para os registros que não possuem ano de início e/ou fim.

#converte as colunas para string
df['start_year'] = df['start_year'].astype(str)
df['end_year'] = df['end_year'].astype(str)

# Substitui todos os caracteres que não são dígitos por uma string vazia
df['start_year'] = df['start_year'].replace(to_replace=r'\D', value='', regex=True)
df['end_year'] = df['end_year'].replace(to_replace=r'\D', value='', regex=True)

# substitui os valores vazios por 0
df['start_year'] = df['start_year'].replace(to_replace=r'^\s*$', value='0', regex=True)
df['end_year'] = df['end_year'].replace(to_replace=r'^\s*$', value='0', regex=True)

# converte as colunas para int
df['start_year'] = df['start_year'].astype(int)
df['end_year'] = df['end_year'].astype(int)

Vamos conferir a quantidade de registros por ano de início e fim, lembrando que o número 0 representa os registros que não possuem ano de início e/ou fim:

Primeiro, criar um dataframe com a contagem dos registros por ano de início:

# contar início da publicação
df_bdate = df['start_year'].value_counts().rename_axis('Ano').reset_index(name='quantidade')
# organizar por ano
df_bdate = df_bdate.sort_values(by=['Ano'])

Vamos avaliar os dados iniciais do dataframe para encontrar possíveis erros:

df_bdate.head(10)
Ano quantidade
0 0 425
181 9 2
200 83 1
210 87 1
199 88 1
196 95 1
208 1521 1
206 1691 1
203 1741 1
201 1763 1

Percemos que 5 registros possuem registros que fogem do oadrão YYYY e, sendo estatisticamente insignificantes, serão excluídos do dataframe.

# excluir registros com ano entre 2 e 999
df_bdate = df_bdate[(df_bdate['Ano'] < 1) | (df_bdate['Ano'] > 999)]

Faremos o mesmo para final do dataframe:

df_bdate.tail(10)
Ano quantidade
162 2005 6
186 2006 2
195 2007 1
187 2010 2
209 2011 1
177 2012 3
183 2014 2
213 2017 1
198 2021 1
211 2022 1

Não há erros no fim. Podemos prosseguir para a visualização dos dados.

# excluir registros menor que 1000
df_bdate = df_bdate[df_bdate['Ano'] > 999]
# Criar scatter plot com os dados de datas de publicação com as datas de publicação no eixo Y
fig2 = px.scatter(df_bdate, x='Ano', y='quantidade', title='Datas de início de publicação')
# adcionar mais anos no eixo x
fig2.update_xaxes(range=[1500, 2023])
fig2

Realizaremos os mesmo procedimentos para a coluna do ano de fim:

# contar fim da publicação
df_edate = df['end_year'].value_counts().rename_axis('Ano').reset_index(name='quantidade')
# organizar por ano
df_edate = df_edate.sort_values(by=['Ano'])
df_edate.head(10)
Ano quantidade
0 0 425
186 9 2
202 83 1
215 87 1
218 88 1
208 95 1
210 1521 1
217 1694 1
216 1741 1
214 1767 1
df_edate.tail(10)
Ano quantidade
188 2016 2
181 2017 3
180 2018 3
206 2019 1
207 2020 1
193 2021 2
179 2022 3
196 2023 2
205 4949 1
201 9924 1
# excluir registros com ano entre 2 e 999 e acima de 2023
df_edate = df_edate[(df_edate['Ano'] < 1) | (df_edate['Ano'] > 999)]
df_edate = df_edate[df_edate['Ano'] < 2023]
# criar scatter plot com os dados de datas de publicação com as datas de publicação no eixo Y
fig3 = px.scatter(df_edate, x='Ano', y='quantidade', title='Datas de término de publicação')
# adcionar mais anos no eixo x
fig3.update_xaxes(range=[1500, 2023])
# limitar eixo y a 200
fig3.update_yaxes(range=[0, 200])
fig3.show()
# criar um scatter plot com os dados de datas de início e fim de publicação
fig4 = px.scatter(df_bdate, x='Ano', y='quantidade', title='Datas de início e término de publicação')
# adicionar df_edate ao gráfico
fig4.add_scatter(x=df_edate['Ano'], y=df_edate['quantidade'], mode='lines', name='Término de publicação')
# adcionar mais anos no eixo x
fig4.update_xaxes(range=[1500, 2023])
# limitar eixo y entre 0 e 200
fig4.update_yaxes(range=[0, 200])
# mostrar legenda para df_bdate
fig4.update_layout(legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01))
fig4.show()

Vamos calcular a quantidade de publicações por ano de início agrupadas por década (aqui vamos seguir o padrão mais comum no acervo da HDB, 1900 - 1909 corresponde à década de 1900, 1910 - 1919 corresponde à década de 1910, e assim por diante):

# criar coluna com década de início de publicação
df['start_decade'] = df['start_year'].astype(str).str[:3] + '0'
# contar década de início de publicação e csalvar em um dataframe
df_bdecade = df['start_decade'].value_counts().rename_axis('Década').reset_index(name='quantidade')
df_bdecade = df_bdecade.sort_values(by=['Década'])
# tornar a coluna 'Década' em inteiro
df_bdecade['Década'] = df_bdecade['Década'].astype(int)
df_bdecade
Década quantidade
6 0 425
30 1520 1
29 1690 1
25 1740 1
27 1760 1
22 1800 2
20 1810 16
12 1820 149
8 1830 387
10 1840 327
9 1850 350
5 1860 497
2 1870 781
0 1880 1277
1 1890 1101
3 1900 530
4 1910 506
7 1920 417
11 1930 304
13 1940 138
14 1950 131
17 1960 84
15 1970 97
16 1980 96
18 1990 31
19 2000 18
21 2010 9
24 2020 2
31 830 1
28 870 1
32 880 1
23 90 2
26 950 1
# excluir registros com década menor que 1500 e maior que 2023
df_bdecade = df_bdecade[(df_bdecade['Década'] > 1500) & (df_bdecade['Década'] < 2023)]
# criar um gráfico de barras com os dados de década de início de publicação
fig5 = bar_chart(df_bdecade, 'Década', 'quantidade', 'Décadas de início de publicação (com mais de 1 publicação)', range_x=[1800, 2023], range_y=[0, 1400], color=None)
# adcionar texto nas barras
fig5.update_traces(texttemplate='%{y}', textposition='inside')
fig5.show()
# criar coluna com década de término de publicação
df['end_decade'] = df['end_year'].astype(str).str[:3] + '0'
# contar década de término de publicação e salvar em um dataframe
df_edecade = df['end_decade'].value_counts().rename_axis('Década').reset_index(name='quantidade')
df_edecade = df_edecade.sort_values(by=['Década'])
# tornar a coluna 'Década' em inteiro
df_edecade['Década'] = df_edecade['Década'].astype(int)
df_edecade
Década quantidade
6 0 425
32 1520 1
28 1690 1
30 1740 1
29 1760 1
27 1780 1
21 1810 9
14 1820 133
8 1830 375
11 1840 305
10 1850 334
5 1860 470
2 1870 728
0 1880 1284
1 1890 1092
3 1900 542
4 1910 478
7 1920 397
9 1930 353
13 1940 147
12 1950 168
17 1960 108
16 1970 120
15 1980 121
18 1990 35
20 2000 20
19 2010 20
22 2020 8
26 4940 1
31 830 1
33 870 1
34 880 1
23 90 2
24 950 1
25 9920 1
# excluir registros com década menor que 1500 e maior que 2023
df_edecade = df_edecade[(df_edecade['Década'] > 1500) & (df_edecade['Década'] < 2023)]
# criar um gráfico de barras com os dados de década de término de publicação
fig6 = bar_chart(df_edecade, 'Década', 'quantidade', 'Décadas de término de publicação (com mais de 1 publicação)', range_x=[1800, 2023], range_y=[0, 1400], color=None)
# adcionar texto nas barras
fig6.update_traces(texttemplate='%{y}', textposition='inside')
fig6.show()
# criar um gráfico de barras com os dados de década de início e término de publicação
fig7 = bar_chart(df_bdecade, 'Década', 'quantidade', 'Décadas de início e término de publicação (com mais de 1 publicação)', range_x=[1800, 2023], range_y=[0, 2600], color=None)
# adcionar texto nas barras
fig7.update_traces(texttemplate='%{y}', textposition='inside')
# adicionar df_edecade ao gráfico
fig7.add_bar(x=df_edecade['Década'], y=df_edecade['quantidade'], name='Término de publicação')
# adcionar texto nas barras de df_edecade
fig7.update_traces(texttemplate='%{y}', textposition='inside')
# mostrar legenda para df_bdecade
fig7.update_layout(legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01))
fig7.show()

Vamos avaliar quantos registros possuem o mesmo ano de início e fim:

# contar quando início e fim da publicação são iguais
df[df['start_year'] == df['end_year']].count()
title           5694
subtitle        2874
place           5626
period          5274
publisher       4155
periodicity     4507
language        5694
start_year      5694
end_year        5694
start_decade    5694
end_decade      5694
dtype: int64

Em 5694 registros, o ano de início da publicação é o mesmo do ano de término, indicando que 7265 periódicos que possuem data registrada no dataframe, apenas 1569 possuem data de início e término diferentes. Isso corresponde a uma porcentagem de 21,6% dos periódicos.

Vamos criar uma nova coluna contando a quantidade de anos presentes no acervo de cada periódico:

# calcular a diferença entre início e fim da publicação
df['diff'] = df['end_year'] - df['start_year']
# listar periódicos com maiores diferenças entre início e fim da publicação
df.sort_values(by='diff', ascending=False).head(20)
title subtitle place period publisher periodicity language start_year end_year start_decade end_decade diff
5880 O Povo orgam do Partido Popular Pitangui, MG 19924 NaN Semanal não definido 1992 9924 1990 9920 7932
4303 O Abaete : jornal noticioso a servico do progr... NaN Abaeté, MG 1948-4949 [s.n.] NaN por 1948 4949 1940 4940 3001
2489 Diário de Pernambuco NaN Recife, PE 1825-1984 Diário de Pernambuco Diária não definido 1825 1984 1820 1980 159
2286 Correio Official de Goyaz NaN Goiás, GO 1837-1943 Typ. Provincial Desconhecida por 1837 1943 1830 1940 106
3659 Jornal do Commercio NaN Manaus, AM 1904-2007 Empresa Jornal do Commercio Diária não definido 1904 2007 1900 2000 103
3473 Imprensa e Lei NaN Lisboa [Portugal] 1854-1956 NaN Indeterminada por 1854 1956 1850 1950 102
1307 Almanak do Ministerio da Marinha NaN Rio de Janeiro, RJ 1858-1960 NaN Anual por 1858 1960 1850 1960 102
6990 Revista do Clube de Engenharia NaN Rio de Janeiro, RJ 1887-1989 O Clube NaN por 1887 1989 1880 1980 102
770 A Nova Era NaN Aracaju, SE 1889-1990 [s.n.] Desconhecida por 1889 1990 1880 1990 101
2597 Documentos Acta dos festejos civicos com que o povo de So... Sobral, CE 1822-1922 NaN NaN não definido 1822 1922 1820 1920 100
2551 Diario Official [do] Imperio do Brasil NaN Rio de Janeiro, RJ 1862-1959 Imprensa Oficial NaN por 1862 1959 1860 1950 97
2827 Estrella do Minho folha noticiosa, litteraria e bibliographica Vila Nova de Famalicão [Portugal] 1893-1987 NaN Semanal por 1893 1987 1890 1980 94
4516 O Beijo litterário crítico e artistico Manaus, AM 1897-1988 Typ. do Amazonas Commercial Indeterminada não definido 1897 1988 1890 1980 91
5345 O Juvenil NaN Bom Sucesso, MG 1892-1979 NaN Semanal não definido 1892 1979 1890 1970 87
1866 Casamentos Alemães NaN Blumenau, SC 1900-1985 NaN Indeterminada por 1900 1985 1900 1980 85
1192 Alagoas Livre NaN Recife, PE 1817-1901 [s.n.] Semanal por 1817 1901 1810 1900 84
3170 Gazeta de Noticias NaN Rio de Janeiro, RJ 1875-1956 Typ. da Gazeta de Noticias Diária por 1875 1956 1870 1950 81
3797 Kolonie Zeitung NaN Joinville, SC 1862-1942 Typ. de C.W. Boehm Indeterminada por 1862 1942 1860 1940 80
7535 União Médica archivo internacional de sciencias medicas Rio de Janeiro, RJ 1811-1890 NaN Desconhecida por 1811 1890 1810 1890 79
2985 Folha do Norte NaN Belém, PA 1896-1974 Typ. da Folha do Norte Diária por 1896 1974 1890 1970 78

Percebemos que os dois primeiro registros possuem contagem acima de 250 anos, o que representa um erro no dataframe. Na análise a seguir, vamos levar em conta apenas registros com no máximo 500 anos de publicação.

Vamos visualizar os periódicos com mais de 50 anos de publicação presentes no acervo:

# a coluna diff deve ter entre 50 e 200 anos
df_diff = df[(df['diff'] > 50) & (df['diff'] < 500)]
# organizar por diferença
df_diff = df_diff.sort_values(by='diff', ascending=False)
# excluir linhas com títulos, locais e anos de início iguais
df_diff = df_diff.drop_duplicates(subset=['title', 'place', 'start_year'], keep='first')
# criar gráfico
fig8 = bar_chart(df_diff, 'title', 'diff', 'Periódicos com maiores diferenças entre início e fim da publicação', range_x=None, range_y=None, color=None)
# adcionar texto nas barras
fig8.update_traces(texttemplate='%{y}', textposition='inside')
fig8.show()
# contar 'place' iguais no df_diff com mais de 10 anos de diferença
df_diff[df_diff['diff'] > 2]['place'].value_counts()
Rio de Janeiro, RJ                   21
Recife, PE                            3
Juiz de Fora, MG                      2
São Paulo, SP                         2
Blumenau, SC                          2
Manaus, AM                            2
Curitiba, PR                          1
Porto Alegre, RS                      1
Olinda, PE                            1
Rio Grande, RS                        1
Laguna, SC                            1
Brasília, DF                          1
Paraná                                1
Lages, SC                             1
São Leopoldo, RS                      1
Desterro [Florianópolis, SC]          1
São Paulo                             1
São Luis, MA                          1
Santa Catarina                        1
Pará, PA                              1
Leopoldina, MG                        1
Goiás, GO                             1
Uberaba, MG                           1
Garibaldi, RS                         1
Canoinhas, SC                         1
Belém, PA                             1
Joinville, SC                         1
Bom Sucesso, MG                       1
Vila Nova de Famalicão [Portugal]     1
Sobral, CE                            1
Aracaju, SE                           1
Lisboa [Portugal]                     1
Bom Despacho, MG                      1
Name: place, dtype: int64
# criar gráfico com locais mais de 10 anos de diferença
df_diff_place = df_diff[df_diff['diff'] > 10]['place'].value_counts().rename_axis('Local').reset_index(name='quantidade')
# organizar por quantidade
df_diff_place = df_diff_place.sort_values(by='quantidade', ascending=False)
# criar gráfico
fig9 = bar_chart(df_diff_place, 'Local', 'quantidade', 'Locais com maior número de publicações com mais de 10 anos de diferença entre início e fim da publicação', range_x=None, range_y=None, color=None)
fig9.show()

Existem 21 periódicos com mais de 10 anos de publicação na cidade do Rio de Janeiro. Em seguida, temos Recife, com 3 periódicos, seguido de São Paulo, Juiz de Fora, Blumenau e Manaus, com 2 periódicos cada. Temos 27 cidades com 1 periódico cada na sequência.

Periodicidade#

# contar periodicidade
df['periodicity'].value_counts()
Desconhecida                  2085
Semanal                       1505
Mensal                         425
Indeterminada                  354
Quinzenal                      308
Diária                         258
2 vezes por semana             257
Anual                          170
Irregular                       94
2 vezes por mês                 72
Bissemanal                      61
3 vezes por semana              59
3 vezes por mês                 31
Número único                    29
Duas vezes por semana           28
Trimestral                      28
Bimensal                        26
Bimestral                       17
4 vezes por mes                  8
semanal: nos domingos            8
Varia                            8
Bi-mensal                        7
Trimensal                        7
Semisemanal                      5
Semestral                        5
Três vezes por semana            4
6 vezes por mês                  3
sábados                          3
N. especial                      3
semanal: domingo                 2
semanal: no sábados              2
3 vezes ao mes                   2
semanal: quarta-feira            2
Edição única                     2
terça e sexta-feira              2
Mensal?                          2
Quinzenal [1ª fase]              2
Duas vezes na semana             1
4 vezes por semana               1
Trimestral.(1ª e 2ª Fases)       1
Semanal, as quintas              1
Semanal: sabado                  1
u                                1
Quadrimestral                    1
6 vezes por semana               1
Bissemestral                     1
5 vezes por semana               1
quarta-feira e sábado            1
Sabado                           1
semanal: todo sábado             1
Mensal (Edicao especial)         1
2 veses por mês                  1
Regular                          1
Trissemanal                      1
Name: periodicity, dtype: int64

É possível perceber que os dados referentes à periodicidade dos periódicos também não estão padronizados. Vamos minimizar as diferenças entre as formas de registro dos dados para facilitar a análise.

# padronizar valores na coluna 'periodicity'
# substituir 2 por Duas
df['periodicity'] = df['periodicity'].replace(to_replace=r'2', value='Duas', regex=True)
# substituir 3 por Três
df['periodicity'] = df['periodicity'].replace(to_replace=r'3', value='Três', regex=True)
# substituir 4 por Quatro
df['periodicity'] = df['periodicity'].replace(to_replace=r'4', value='Quatro', regex=True)
# substituir ' mes' por ' mês' 
df['periodicity'] = df['periodicity'].replace(to_replace=r' mes', value=' mês', regex=True)
# substituir Bi-mensal por Bimensal
df['periodicity'] = df['periodicity'].replace(to_replace=r'Bi-mensal', value='Bimensal', regex=True)
# substituir ' veses' por ' vezes'
df['periodicity'] = df['periodicity'].replace(to_replace=r' veses', value=' vezes', regex=True)
# colocar todos os valores em minúsculo
df['periodicity'] = df['periodicity'].str.lower()

Agora, vejamos a porcentagem de registros com periodicidade definida:

# contar porcentagem de periodicidade
df['periodicity'].value_counts(normalize=True)
desconhecida                     0.353330
semanal                          0.255042
mensal                           0.072022
indeterminada                    0.059990
quinzenal                        0.052195
duas vezes por semana            0.048297
diária                           0.043721
anual                            0.028809
irregular                        0.015930
duas vezes por mês               0.012371
três vezes por semana            0.010676
bissemanal                       0.010337
bimensal                         0.005592
três vezes por mês               0.005253
número único                     0.004914
trimestral                       0.004745
bimestral                        0.002881
varia                            0.001356
semanal: nos domingos            0.001356
quatro vezes por mês             0.001356
trimensal                        0.001186
semisemanal                      0.000847
semestral                        0.000847
6 vezes por mês                  0.000508
n. especial                      0.000508
sábados                          0.000508
edição única                     0.000339
semanal: domingo                 0.000339
mensal?                          0.000339
semanal: no sábados              0.000339
quinzenal [1ª fase]              0.000339
terça e sexta-feira              0.000339
semanal: quarta-feira            0.000339
três vezes ao mês                0.000339
semanal, as quintas              0.000169
u                                0.000169
trimestral.(1ª e duasª fases)    0.000169
quadrimestral                    0.000169
duas vezes na semana             0.000169
quatro vezes por semana          0.000169
semanal: sabado                  0.000169
quarta-feira e sábado            0.000169
bissemestral                     0.000169
5 vezes por semana               0.000169
6 vezes por semana               0.000169
sabado                           0.000169
semanal: todo sábado             0.000169
mensal (edicao especial)         0.000169
regular                          0.000169
trissemanal                      0.000169
Name: periodicity, dtype: float64

E visualizar os dados com um gráfico de pizza:

# Criar dataframe com valores de periodicidade maiores que 1%
df_periodicity = df['periodicity'].value_counts(normalize=True)
df_periodicity = df_periodicity[df_periodicity > 0.01]
# gráfico de pizza
fig10 = px.pie(df_periodicity, values='periodicity', names=df_periodicity.index, title='Porcentagem de periodicidade')
fig10.show()

Se excluirmos os registros sem periodicidade conhecida (que correspondem a 36,7%), percebemos que parte significativa do acervo, 41.9%, é composto por periódicos semanais. Em seguida, representando 11.8% do acervo temos periódicos mensais. Periódicos diários correspondem a apenas 7.17% do acervo.

Caso consideremos todo o acervo, incluindo periódicos com periodicidade desconhecida, as publicações semanais correspondem a 26.5%, mensais a 7.48% e diárias a 4.54%.

Locais de publicação#

# contar locais de publicação
df['place'].value_counts()
Rio de Janeiro, RJ      2185
São Paulo, SP            294
Florianópolis, SC        239
Recife, PE               207
Bahia [Salvador, BA]     192
                        ... 
Lageado, MT                1
Joinvelle                  1
Tiradentes, MG             1
Duque de Caxias, RJ        1
Urussanga                  1
Name: place, Length: 672, dtype: int64

Vamos contar quantos registros não possuem local de publicação definido e em seguida, incluir o valor ‘indeterminado’ para esses registros:

# contar place is null
df[df['place'].isnull()].count()
title           77
subtitle        39
place            0
period          42
publisher       20
periodicity     50
language        77
start_year      77
end_year        77
start_decade    77
end_decade      77
diff            77
dtype: int64
# adicionar 'indeterminado' nos locais nulos
df['place'] = df['place'].fillna('indeterminado')

Vamos criar um dataframe com os dados de cidades

# criar df com locais
df_place = df['place'].value_counts()
df_place = df_place.rename_axis('Cidades').reset_index(name='Quantidade')
# colocar Local em minúsculo
df_place['Cidades'] = df_place['Cidades'].str.lower()
# substituir [s.l.] por indeterminado
df_place['Cidades'] = df_place['Cidades'].replace(to_replace=r'\[s.l.\]', value='indeterminado', regex=True)
df_place
Cidades Quantidade
0 rio de janeiro, rj 2185
1 são paulo, sp 294
2 florianópolis, sc 239
3 recife, pe 207
4 bahia [salvador, ba] 192
... ... ...
668 santa cruz do sul ,rs 1
669 demétrio ribeiro [joão neiva, es] 1
670 jaguarana 1
671 águas de sao pedro, sp 1
672 urussanga 1

673 rows × 2 columns

# criar gráfico de barras com os 20 locais com mais registros
fig11 = bar_chart(df_place.head(20), 'Cidades', 'Quantidade', '20 Cidades com mais registros', range_x=None, range_y=None, color=None)
# adcionar texto nas barras
fig11.update_traces(texttemplate='%{y}', textposition='inside')
fig11.show()

Vamos transformar esses dados em porcentagem e considerar apenas cidades com mais de 1% do total de registros:

#Criar gráfico de pizza com a porcentagem de periodicidade acima de 0.01
df_place = df['place'].value_counts(normalize=True)
df_place = df_place[df_place > 0.01]
df_place = df_place.rename_axis('Cidades').reset_index(name='Porcentagem')
# transformar porcentagem em número
df_place['Porcentagem'] = df_place['Porcentagem'] * 100
# mostrar apenas 3 casas decimais
df_place['Porcentagem'] = df_place['Porcentagem'].round(2)
df_place
Cidades Porcentagem
0 Rio de Janeiro, RJ 28.43
1 São Paulo, SP 3.83
2 Florianópolis, SC 3.11
3 Recife, PE 2.69
4 Bahia [Salvador, BA] 2.50
5 Maceió, AL 2.41
6 Curitiba, PR 1.85
7 Manaus, AM 1.69
8 [S.l.] 1.68
9 Aracaju, SE 1.55
10 São Luis, MA 1.46
11 Desterro [Florianópolis, SC] 1.35
12 Porto Alegre, RS 1.35
13 Fortaleza, CE 1.24
14 Belém, PA 1.17
15 Fortaleza 1.07
16 Ouro Preto, MG 1.00
17 indeterminado 1.00
18 Pernambuco 1.00
# gráfico de pizza
fig12 = px.pie(df_place, values='Porcentagem', names=df_place['Cidades'], title='Cidades com mais de 1% dos registros')
# valor absoluto no gráfico de pizza ao invés de porcentagem e acrescentar o caractere %
fig12.update_traces(textinfo='value', textfont_size=12, texttemplate='%{value:.2f}%')
fig12.show()

A cidade do Rio de Janeiro corresponde a 28,43% do total e 47,1% entre as cidades com pelo menos 1%. A cidade de São Paulo vem em seguida, mas com apenas 3,82% do total e 6,6% entre as cidades com pelo menos 1%. A diferença entre São Paulo e as seguintes é pequena: Florianópolis com 3,1%; Recife com 2,69%; Salvador com 2,49% e Maceió com 2,40%

# criar coluna com estado, pegando apenas o padrão ', dd' na coluna 'Local'
df['Estado'] = df['place'].str.extract(r',\s(\w\w)')
# remover ', ' da coluna 'Estado'
df['Estado'] = df['Estado'].str.replace(', ', '')
#colocar estado em maiúsculo
df['Estado'] = df['Estado'].str.upper()
df.head()  
title subtitle place period publisher periodicity language start_year end_year start_decade end_decade diff Estado
0 O Abolicionista Paraense NaN Belém, PA 1883- Typ. da Provincia do Para semanal por 1883 1883 1880 1880 0 PA
1 O Abolicionista propriedade de uma associação São Luis, MA 1885 Typ. do Abolicionista NaN por 1885 1885 1880 1880 0 MA
2 O Academico periodico scientifico, litterario e especialme... Rio de Janeiro, RJ 1855- Typ. Fluminense, de D.L. dos Santos NaN por 1855 1855 1850 1850 0 RJ
3 A Actualidade orgao do Partido Liberal Ouro Preto, MG 1878-1882 Typ. de Jose Egydio da Silca Campos três vezes por semana não definido 1878 1882 1870 1880 4 MG
4 A Actualidade periodico imparcial, litterario, critico e not... Maranhão 1900- Typ. de Antonio Pereira Ramos d'Almeida e C. S... três vezes por mês por 1900 1900 1900 1900 0 NaN
# agrupar por estado e contar valores únicos da coluna 'place'
df_state = df.groupby('Estado')['place'].nunique().rename_axis('Estado').reset_index(name='Quantidade')
# ordenar por quantidade
df_state = df_state.sort_values(by='Quantidade', ascending=False)
df_state
Estado Quantidade
12 MG 190
28 SP 59
26 SC 49
21 RJ 48
25 RS 27
4 BA 20
7 ES 20
5 CE 18
1 AL 16
15 PA 13
20 PR 12
2 AM 11
27 SE 11
18 PI 9
17 PE 7
0 AC 6
13 MS 6
11 MA 6
16 PB 4
14 MT 4
10 GO 4
22 RN 3
19 PO 2
24 RR 2
6 DF 2
29 TO 2
23 RO 1
9 FR 1
8 EU 1
3 AP 1

Apesar da centralidade da cidade do Rio de Janeiro nos números absolutos do acervo, é interessante notar que o estado do Rio de Janeiro possui apenas 48 cidades entre as localidades dos periódicos, atrás de Santa Catarina (49), São Paulo (59) e Minas Gerais, que apresenta o maior número de cidades no dataframe, com 190.

# gráfico de barras com a quantidade de registros por estado
fig13 = bar_chart(df_state, 'Estado', 'Quantidade', 'Quantidade de cidades por estado', range_x=None, range_y=None, color=None)
# adicionar texto nas barras
fig13.update_traces(texttemplate='%{y}', textposition='inside')
fig13.show()

Editoras#

# colocar editor em minúsculo
df['publisher'] = df['publisher'].str.lower()
# substituir Typografia e Tipografia por Typ.
df['publisher'] = df['publisher'].replace(to_replace=r'typografia', value='typ.', regex=True)
# substituir [s.n], s.n], [s.n por Não identificado
df['publisher'] = df['publisher'].replace(to_replace=r'\[s.n\]', value='não identificado', regex=True)
df['publisher'] = df['publisher'].replace(to_replace=r's.n.\]', value='não identificado', regex=True)
df['publisher'] = df['publisher'].replace(to_replace=r'\[s.n.', value='não identificado', regex=True)
df['publisher'] = df['publisher'].replace(to_replace=r's.n.', value='não identificado', regex=True)
df['publisher'] = df['publisher'].replace(to_replace=r'\[não identificado', value='não identificado', regex=True)
# contar editor
df['publisher'].value_counts()
não identificado                                       1800
typ. do diário                                           45
typ. nacional                                            43
typ. americana                                           33
typ. cosmopolita                                         32
                                                       ... 
typ. do brasil                                            1
diário de pernambuco                                      1
typ. de j.a. dos não identificadoos cardoso e irmao       1
typ. commercial, de a.j. da costa                         1
typ. l. faria & cia.                                      1
Name: publisher, Length: 2014, dtype: int64
# criar df com editor
df_editor = df['publisher'].value_counts().rename_axis('Editor/a').reset_index(name='Quantidade')
df_editor
Editor/a Quantidade
0 não identificado 1800
1 typ. do diário 45
2 typ. nacional 43
3 typ. americana 33
4 typ. cosmopolita 32
... ... ...
2009 typ. do brasil 1
2010 diário de pernambuco 1
2011 typ. de j.a. dos não identificadoos cardoso e ... 1
2012 typ. commercial, de a.j. da costa 1
2013 typ. l. faria & cia. 1

2014 rows × 2 columns

# gráfico de barras com os 20 editores com mais registros
fig14 = bar_chart(df_editor.head(20), 'Editor/a', 'Quantidade', '20 Editores com mais registros', range_x=None, range_y=None, color=None)
# adicionar texto nas barras
fig14.update_traces(texttemplate='%{y}', textposition='inside')
fig14.show()
# criar o mesmo gráfico mas sem o editor 'Não identificado'
fig15 = bar_chart(df_editor[1:21], 'Editor/a', 'Quantidade', '20 Editores com mais registros', range_x=None, range_y=None, color=None)
# adicionar texto nas barras
fig15.update_traces(texttemplate='%{y}', textposition='inside')
fig15.show()
# avaliar principais editores por estado
df_state_editor = df.groupby(['Estado', 'publisher'])['place'].nunique().rename_axis(['Estado', 'Editor/a']).reset_index(name='Quantidade')
# remover editor 'Não identificado'
df_state_editor = df_state_editor[df_state_editor['Editor/a'] != 'não identificado']
df_state_editor
Estado Editor/a Quantidade
1 AC offic. d'o municipio 1
2 AC prefeitura do alto purus, 1908- 1
3 AC typ. da folha do acre 1
4 AC typ. imprensa oficial do estado do acre 1
5 AC typ. la paz 1
... ... ... ...
2076 SP typ. união 1
2077 SP typ. vanordem e cia 1
2078 SP typ. vanorden e cia 1
2079 SP typo-lithographia ribeiro 1
2080 SP typographia brasil de carlos gerke 1

2058 rows × 3 columns

# contar editor por estado
df_state_editor = df_state_editor.groupby('Estado')['Editor/a'].nunique().rename_axis('Estado').reset_index(name='Quantidade')
df_state_editor = df_state_editor.sort_values(by='Quantidade', ascending=False)
df_state_editor
Estado Quantidade
19 RJ 755
25 SP 185
10 MG 176
4 BA 132
23 SC 101
1 AL 97
15 PE 80
22 RS 77
13 PA 72
9 MA 64
18 PR 58
5 CE 58
24 SE 45
2 AM 39
7 ES 30
20 RN 26
16 PI 26
12 MT 19
0 AC 6
6 DF 4
8 GO 2
11 MS 2
21 RO 1
3 AP 1
14 PB 1
17 PO 1

Palavras mais frequentes nos títulos e subtítulos#

Vamos analisar as palavras mais frequentes nos títulos e subtítulos dos periódicos digitalizados (desde que tenham mais de 4 caracteres).

# criar dataframe com a quantidade de títulos
df_title = df['title'].str.lower().value_counts().rename_axis('Título').reset_index(name='Quantidade')
df_title
Título Quantidade
0 o progresso 37
1 o municipio 30
2 o democrata 25
3 o imparcial 22
4 o commercio 21
... ... ...
5365 ensaio litterario 1
5366 ensaio juridico e litterario 1
5367 ensaio escolatico dos estudantes do atheneo tu... 1
5368 semanario do cincinnato 1
5369 o zig-zag 1

5370 rows × 2 columns

# listar os 20 títulos com mais registros
df_title.head(20).style.hide(axis="index")
Título Quantidade
o progresso 37
o municipio 30
o democrata 25
o imparcial 22
o commercio 21
o liberal 20
jornal do commercio 19
o povo 18
o popular 18
a ordem 18
o constitucional 18
o trabalho 18
o conservador 17
o tempo 16
relatorios dos presidentes dos estados brasileiros : primeira republica 16
a noticia 16
a verdade 15
a semana 14
a luz 14
jornal do povo 13
# ler a coluna título e, df_title e contar palavras únicas e a quantidade de vezes que aparecem
# mostrar apenas palavras mais longas que 3 caracteres
df_title_words = df['title'].str.lower().str.split(expand=True).stack().value_counts().rename_axis('Palavra').reset_index(name='Quantidade')
df_title_words = df_title_words[df_title_words['Palavra'].str.len() > 3]
df_title_words.head(50).style.hide(axis="index")
Palavra Quantidade
jornal 396
revista 310
gazeta 220
correio 212
folha 151
diario 147
povo 147
litterario 138
orgao 104
echo 98
commercio 97
periodico 96
commercial 90
tribuna 87
estado 85
noticioso 82
liberal 81
almanak 69
brazil 67
brasil 64
para 64
popular 62
progresso 62
orgam 60
almanach 60
norte 57
municipio 56
provincia 54
janeiro 52
republica 51
boletim 51
constitucional 51
mercantil 50
conservador 50
cidade 49
nova 49
nacional 48
tarde 48
minas 47
interesses 47
fluminense 45
semana 44
litteraria 43
imparcial 43
liberdade 43
partido 43
recreativo 42
verdade 42
official 42
critico 41
# gráfico de barras com as 50 palavras mais frequentes
fig16 = bar_chart(df_title_words.head(50), 'Palavra', 'Quantidade', 'Títulos - 50 palavras mais frequentes', range_x=None, range_y=None, color=None)
# adicionar texto nas barras
fig16.update_traces(texttemplate='%{y}', textposition='inside')
fig16.show()
# criar dataframe com a quantidade de subtítulos
df_subtitle = df['subtitle'].str.lower().value_counts().rename_axis('Subtítulo').reset_index(name='Quantidade')
df_subtitle
Subtítulo Quantidade
0 orgam republicano 22
1 orgam imparcial 17
2 periodico litterario e recreativo 16
3 periodico litterario e noticioso 14
4 orgam do partido republicano 14
... ... ...
2744 revista dos interesses publicos 1
2745 orgam do gremio i e b dos empregados no commercio 1
2746 periodico semanal, literario e recreativo 1
2747 jornal redigido por academicos 1
2748 periodico encyclopedico 1

2749 rows × 2 columns

# listar os 20 subtítulos com mais registros
df_subtitle.head(20).style.hide(axis="index")
Subtítulo Quantidade
orgam republicano 22
orgam imparcial 17
periodico litterario e recreativo 16
periodico litterario e noticioso 14
orgam do partido republicano 14
periodico critico e litterario 13
periodico scientifico e litterario 13
semanario independente 13
orgao do partido conservador 12
jornal politico 12
jornal politico e noticioso 12
orgao imparcial 12
folha popular 10
jornal independente 10
orgao do partido republicano 10
orgam independente e noticioso 9
jornal politico, litterario e commercial 9
jornal litterario 8
orgam independente 8
periodico critico e chistoso 8
# ler a coluna subtítulo em df_subtitle e contar palavras únicas e a quantidade de vezes que aparecem
# mostrar apenas palavras mais longas que 3 caracteres
df_subtitle_words = df['subtitle'].str.lower().str.split(expand=True).stack().value_counts().rename_axis('Palavra').reset_index(name='Quantidade')
df_subtitle_words = df_subtitle_words[df_subtitle_words['Palavra'].str.len() > 3]
df_subtitle_words.head(50).style.hide(axis="index")
Palavra Quantidade
jornal 609
noticioso 576
litterario 524
orgam 502
orgao 483
periodico 481
orgão 309
interesses 246
critico 220
folha 183
revista 178
commercial 176
semanario 174
litterario, 173
partido 167
republicano 159
politico 143
politico, 139
dedicado 128
recreativo 118
independente 112
critico, 98
humoristico 97
litteraria 93
imparcial 79
noticioso, 75
mensal 63
commercial, 61
municipio 60
commercio 59
scientifico 58
sociedade 56
propaganda 56
povo 54
para 52
noticiosa 51
club 51
semanal 51
conservador 50
hebdomadario 50
literario 49
liberal 48
popular 46
artes 44
industria 43
illustrado 39
official 38
politica, 38
estado 35
lavoura 35
# gráfico de barras com as 50 palavras mais frequentes
fig17 = bar_chart(df_subtitle_words.head(50), 'Palavra', 'Quantidade', 'Subtítulos - 50 palavras mais frequentes', range_x=None, range_y=None, color=None)
# adicionar texto nas barras
fig17.update_traces(texttemplate='%{y}', textposition='inside')
fig17.show()

Dataframe final#

Vamos criar um dataframe com as novas colunas e dados limpos criados ao longo desse notebook:

# save df to csv
df.to_csv('../../../data/dataframe_hdb.csv', index=False)

Considerações finais#

Os dados da HDB abrem possibilidades de análise aprofundada de seu acervo, limitações e vieses, assim como permite um estudo da própria história da imprensa no Brasil. De maneira geral, percebemos que o acervo apresenta uma centralidade para periódicos publicados na cidade do Rio de Janeiro, entre os anos 1860 e 1920; periódicos majoritariamente publicados em língua portuguesa; com periodicidade variada, mas com destaque para periódicos semanais e mensais. Também podemos perceber que a vida da maioria dos periódicos foi bastante efêmera, com 5694 periódicos tendo apenas um ano de publicação registrado no acervo de um total de 7685. Isso corresponde a 74,1% dos periódicos. Apenas 1991 periódicos possuem mais de um ano de publicação registrado no acervo, o que corresponde a 25,9% do total.

Muitas reflexões históricas importantes podem ser feitas a partir desses dados, assim como comparações com o acervo da Biblioteca Nacional Digital de Portugal.