Skip to content

View or edit on GitHub

This page is synchronized from trase/models/brazil/soy/brazil_soy_v2_6_1_olam.ipynb. Last modified on 2025-12-14 23:19 CET by Trase Admin. Please view or edit the original file there; changes should be reflected here after a midnight build (CET time), or manually triggering it with a GitHub action (link).

Brazil Soy v2.6.1: OLAM's supply chain

OLAM become a major trader of soy from Brazil in 2019. What do our results say about them?

  • SEI-PCS results from S3, with 2017-18 coming from v2.6.0 and 2019-22 coming from v2.6.1
  • MDIC from S3
  • Comtrade from S3

The results of this notebook along with a discussion and some extra research can be found here:

https://docs.google.com/document/d/16XxH0Yv6wKl3QbbuplQkiRYgK7H8P2nOv-v3UDQio-M/edit#heading=h.32xl9armn1ki

from trase.tools import sps
import plotly.express as px
import pandas as pd

########################################################################################
# Read SEI-PCS results
########################################################################################
dfs = [
    sps.get_pandas_df_once(
        f"brazil/soy/sei_pcs/v{version}/SEIPCS_BRAZIL_SOY_{year}.csv",
        dtype=str,
        na_filter=False,
        print_version_id=True,
    )
    for version, year in [
        ("2.6.0", 2017),
        ("2.6.0", 2018),
        ("2.6.1", 2019),
        ("2.6.1", 2020),
        ("2.6.1", 2021),
        ("2.6.1", 2022),
    ]
]
common_columns = set.intersection(*[set(df.columns) for df in dfs])
df = sps.concat(df[list(common_columns)] for df in dfs)
df = df.astype({"VOLUME_RAW": float, "YEAR": int, "FOB": float})

# fetch company groups from the database
from trase.tools.pandasdb.find import find_traders_and_groups_by_trase_id
from psycopg2 import sql

df_exporter_trase_ids = df[["EXPORTER_TRASE_ID", "YEAR"]].drop_duplicates()
df_exporter_trase_ids[["EXPORTER_GROUP"]] = find_traders_and_groups_by_trase_id(
    df_exporter_trase_ids,
    returning=["group_name"],
    trase_id=sql.Identifier("EXPORTER_TRASE_ID"),
    year=sql.Identifier("YEAR"),
)

df_with_groups = pd.merge(
    df,
    df_exporter_trase_ids,
    on=["EXPORTER_TRASE_ID", "YEAR"],
    how="left",
    validate="many_to_one",
    indicator=True,
)
assert all(df_with_groups.pop("_merge") == "both")

# we say the trade is OLAM if the exporter or exporter group contains "OLAM"
is_olam = df_with_groups["EXPORTER"].str.contains("OLAM") | df_with_groups[
    "EXPORTER_GROUP"
].str.contains("OLAM")

# we make a new column where any of these rows has the exporter group "OLAM", regardless of what the real exporter group in the database was
df_with_groups["MODIFIED_EXPORTER_GROUP"] = (
    df_with_groups["EXPORTER_GROUP"]
    .mask(is_olam, "OLAM")
    .combine_first(df_with_groups["EXPORTER"])
)

# new dataframe with just OLAM trade
df_olam = df_with_groups[is_olam].copy()
df_olam["EXPORTER_AND_GROUP"] = df_olam["EXPORTER"] + " - " + df_olam["EXPORTER_GROUP"]
df_olam["YEARLY_VOLUME"] = df_olam.groupby("YEAR")["VOLUME_RAW"].transform("sum")
df_olam["PERCENTAGE"] = 100 * df_olam["VOLUME_RAW"] / df_olam["YEARLY_VOLUME"]


########################################################################################
# read CNAE
########################################################################################
df_cnae = sps.get_pandas_df_once(
    "brazil/logistics/cnpj/receita_federal_do_brasil/cnaes-2022-11-24.csv",
    dtype=str,
    na_filter=False,
    usecols=["cnae_code", "description"],
    print_version_id=True,
)
/Users/harrybiddle/dev/TRASE/trase/tools/sei_pcs/pandas_utilities.py:18: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.
  from pandas.util.testing import assert_frame_equal


Accessing s3://trase-storage/brazil/soy/sei_pcs/v2.6.0/SEIPCS_BRAZIL_SOY_2017.csv version_id=qKtDR2xK7EEjeI_6eRsVmJa.9A1qfH_b
Accessing s3://trase-storage/brazil/soy/sei_pcs/v2.6.0/SEIPCS_BRAZIL_SOY_2018.csv version_id=j4zTGNLsjTIts0AAg8J.LGP4ycc6KEt9
Accessing s3://trase-storage/brazil/soy/sei_pcs/v2.6.1/SEIPCS_BRAZIL_SOY_2019.csv version_id=a1fTkYc0FghpLMTtpD3_wAsHFE1U6FBi
Accessing s3://trase-storage/brazil/soy/sei_pcs/v2.6.1/SEIPCS_BRAZIL_SOY_2020.csv version_id=nuArlOqWRXyEzXZHeIcaHEs0QNEH8i_c
Accessing s3://trase-storage/brazil/soy/sei_pcs/v2.6.1/SEIPCS_BRAZIL_SOY_2021.csv version_id=ClAQ9M4tm7u4teAyw3ChcpEdjxOFxgef
Accessing s3://trase-storage/brazil/soy/sei_pcs/v2.6.1/SEIPCS_BRAZIL_SOY_2022.csv version_id=Pr5.DDGjAGVkZo8pVrz0qO3kkLhqsX_I
Accessing s3://trase-storage/brazil/logistics/cnpj/receita_federal_do_brasil/cnaes-2022-11-24.csv version_id=WJnPRXxQVHeCJ4xynlunGd1UC_Zfy2ru
data = sps.consolidate(df_olam, ["VOLUME_RAW"], ["YEAR", "EXPORTER_AND_GROUP"])
fig = px.bar(
    data,
    x="YEAR",
    y="VOLUME_RAW",
    color="EXPORTER_AND_GROUP",
    barmode="stack",
    title="OLAM export volume<br />Broken down by <i>exporter - exporter group</i>",
)
fig.show("png")

png

data = sps.consolidate(df_olam, ["VOLUME_RAW"], ["YEAR", "PRODUCT"])
fig = px.bar(
    data,
    x="YEAR",
    y="VOLUME_RAW",
    color="PRODUCT",
    barmode="stack",
    title="OLAM export volume<br />Broken down by product",
)
fig.show("png")

png

To put this in perspective, let's plot the top ten exporters per year.

The smallest of these has around 2.5 - 3B kg per year. OLAM is just outside the top ten.

df_with_groups = df_with_groups[df_with_groups["BRANCH"] != "DOMESTIC"]
df_exporters = sps.consolidate(
    df_with_groups, ["VOLUME_RAW"], ["YEAR", "MODIFIED_EXPORTER_GROUP"]
)
df_exporters["YEARLY_VOLUME"] = df_exporters.groupby("YEAR")["VOLUME_RAW"].transform(
    "sum"
)
df_exporters["PROPORTION"] = df_exporters["VOLUME_RAW"] / df_exporters["YEARLY_VOLUME"]

# find the top ten exporters per year
# there must be a nicer way to do this using group by ...? ah well
top_ten_cutoff_per_year = (
    df_exporters.groupby("YEAR")["PROPORTION"]
    .nlargest(10)
    .reset_index()
    .groupby("YEAR")["PROPORTION"]
    .min()
    .rename("CUTOFF")
    .reset_index()
)
df_exporters = pd.merge(df_exporters, top_ten_cutoff_per_year, on=["YEAR"], how="left")
is_top_ten = df_exporters["PROPORTION"] >= df_exporters["CUTOFF"]

# graph of top ten exporter groups per year
fig = px.bar(
    df_exporters[is_top_ten].sort_values("PROPORTION", ascending=False),
    x="YEAR",
    y="VOLUME_RAW",
    color="MODIFIED_EXPORTER_GROUP",
    barmode="group",
    title="Top ten exporter groups per year",
)
fig.update_layout(
    legend=dict(yanchor="top", y=-0.1, xanchor="left", x=0.01),
    height=800,
)
fig.show("png")

# ranking
print(
    "Ranking of OLAM as an exporter, where 1 represents the biggest exporter in that year"
)
df_exporters["RANKING"] = (
    df_exporters.groupby("YEAR")["PROPORTION"].rank(ascending=False).astype(int)
)
display(
    df_exporters[df_exporters["MODIFIED_EXPORTER_GROUP"] == "OLAM"][["YEAR", "RANKING"]]
)

png

Ranking of OLAM as an exporter, where 1 represents the biggest exporter in that year
YEAR RANKING
231 2017 238
539 2018 19
887 2019 11
1275 2020 12
1541 2021 10
1746 2022 11
data = sps.consolidate(
    df_olam, ["PERCENTAGE"], ["YEAR", "EXPORTER_AND_GROUP", "BRANCH"]
)
fig = px.bar(
    data,
    x="YEAR",
    y="PERCENTAGE",
    color="BRANCH",
    barmode="stack",
    title="OLAM export volume<br />Broken down by <i>exporter - exporter group</i>",
)

fig.show("png")

png

Let's look at OLAM's CNPJs. There are about eight which are important

data = sps.consolidate(df_olam, ["PERCENTAGE"], ["CNPJ", "YEAR"])
data["CNPJ_VOLUME"] = data.groupby("CNPJ")["PERCENTAGE"].transform("sum")
is_top_three_cnpj_per_year = (
    data.groupby("YEAR")["CNPJ_VOLUME"].rank(ascending=False).astype(int) <= 3
)
data["LABEL"] = data["CNPJ"].mask(data["PERCENTAGE"] < 6, "OTHER")
fig = px.bar(
    data.sort_values("CNPJ_VOLUME", ascending=False),
    x="YEAR",
    y="PERCENTAGE",
    color="LABEL",
    title="OLAM CNPJs by export volume",
)
fig.show("png")

png

Let's focus now on 2022 and look at how each CNPJ is solved:

data = df_olam[df_olam["YEAR"] == 2022]
data = sps.consolidate(data, ["PERCENTAGE"], ["CNPJ", "BRANCH"])
fig = px.bar(
    data,
    y="CNPJ",
    x="PERCENTAGE",
    color="BRANCH",
    labels={
        "PERCENTAGE": "Percentage of 2022 export volume",
        "CNPJ": "OLAM CNPJ",
    },
)
fig.show("png")

png

Let's look at this on a map. Most sourcing is from Minas Gerais in the east.

from urllib.request import urlopen
import json

with urlopen(
    #     'https://raw.githubusercontent.com/eupimenta/geoJSON/master/BRMUE250GC_SIR.geojson',
    "https://github.com/luizpedone/municipal-brazilian-geodata/raw/master/data/Brasil.json",
) as response:
    geometry = json.load(response)


data = df_olam[df_olam["YEAR"] == 2022].copy()
STATE_NUMBER_TO_LETTER = {
    "11": "RO",
    "12": "AC",
    "13": "AM",
    "14": "RR",
    "15": "PA",
    "16": "AP",
    "17": "TO",
    "21": "MA",
    "22": "PI",
    "23": "CE",
    "24": "RN",
    "25": "PB",
    "26": "PE",
    "27": "AL",
    "28": "SE",
    "29": "BA",
    "31": "MG",
    "32": "ES",
    "33": "RJ",
    "35": "SP",
    "41": "PR",
    "42": "SC",
    "43": "RS",
    "50": "MS",
    "51": "MT",
    "52": "GO",
    "53": "DF",
    "XX": "XX",
}
data["STATE_GEOCODE"] = (
    data["LVL6_TRASE_ID_LH"].str.slice(3, 5).apply(lambda x: STATE_NUMBER_TO_LETTER[x])
)
data = data[data["STATE_GEOCODE"] != "XX"]
data = sps.consolidate(data, ["VOLUME_RAW"], ["STATE_GEOCODE"])
fig = px.choropleth(
    data,
    geojson=geometry,
    color_continuous_scale="Blues",
    locations="STATE_GEOCODE",
    color="VOLUME_RAW",
    featureidkey="properties.UF",
    title="OLAM 2022 Logistics Hubs<br />Aggregated by state",
)
fig.update_geos(fitbounds="locations")
fig.add_scattergeo(
    geojson=geometry,
    locations=data["STATE_GEOCODE"],
    text=data["STATE_GEOCODE"],
    featureidkey="properties.UF",
    mode="text",
)
fig.show("png")

png

data = df_olam[df_olam["YEAR"] == 2022].copy()
data = sps.consolidate(data, ["PERCENTAGE"], ["LVL6_TRASE_ID_LH"])
fig = px.bar(
    data.sort_values("LVL6_TRASE_ID_LH"),
    y="PERCENTAGE",
    x="LVL6_TRASE_ID_LH",
    title="OLAM 2022 logistics hubs",
)
fig.show("png")

png

Now let's look at the top four 2022 OLAM CNPJs in more detail

# get top four OLAM CNPJs by export volume
df_olam_2022 = df_olam[df_olam["YEAR"] == 2022]
data = sps.consolidate(df_olam_2022, ["PERCENTAGE", "VOLUME_RAW"], ["CNPJ"])
data = data.sort_values("PERCENTAGE", ascending=False)
data = data.head(4)
print("Most important OLAM CNPJs in 2022:\n")
display(data)

# get data from database
from trase.tools.pandasdb.query import query_with_dataframe
from trase.tools.pcs.db import connect_to_db
from trase.tools.pcs.connect import loggingConnection

cnx = connect_to_db()
df_db = query_with_dataframe(
    data,
    "select * from cnpj.cnpj right join df using (cnpj)",
    cnx=loggingConnection(cnx),
    cur=cnx.cursor(),
)
from trase.tools.pcs.reports import print_header

for _, row in df_db.iterrows():
    cnpj = row["cnpj"]
    percentage = row["percentage"]
    volume = row["volume_raw"]

    print_header(f"Focus on CNPJ {cnpj}")
    df_cnpj = df_db[df_db["cnpj"] == cnpj]
    df_results = sps.consolidate(
        df_olam_2022[df_olam_2022["CNPJ"] == cnpj],
        ["VOLUME_RAW"],
        ["LVL6_TRASE_ID_LH", "LVL6_NAME_LH", "BRANCH"],
    )

    pretty_volume_tns = sps.format_float(volume / 100, significant_digits=2)
    print(
        f"Responsible for {percentage:.0f}% of 2022 OLAM export volume, or {pretty_volume_tns} tn\n"
    )

    print("Here is how SEI-PCS locates the logistics hub for this CNPJ:\n")
    fig = px.bar(
        df_results,
        x="VOLUME_RAW",
        y="LVL6_TRASE_ID_LH",
        color="BRANCH",
        title=f"2022 logistics hubs for {cnpj}",
    )
    fig.show("png")

    print("Here is what information we have in the database:")
    display(df_cnpj.T)

    print("Primary CNAE:\n")
    primary_cnae = df_cnpj["cnae"].astype(str).str.rjust(7, "0")
    df_primary_cnae = pd.merge(
        pd.DataFrame({"cnae_code": primary_cnae}),
        df_cnae,
    )
    print(df_primary_cnae.to_string())

    print("\nSecondary CNAEs:\n")
    secondary_cnaes = df_cnpj["cnae_secondary"].str.split(",").explode()
    df_secondary_cnaes = pd.merge(
        pd.DataFrame({"cnae_code": secondary_cnaes}),
        df_cnae,
    )
    print(df_secondary_cnaes.to_string())
Focus on CNPJ 07028528000118
────────────────────────────────────────────────────────────────────────────────
Responsible for 27% of 2022 OLAM export volume, or 6,600,000 tn

Here is how SEI-PCS locates the logistics hub for this CNPJ:

png

Here is what information we have in the database:
0
cnpj 07028528000118
id 40605536
company_name OLAM AGRICOLA LTDA.
juridic_nature 2062
nature_name Sociedade Empresária Limitada
company_social_capital 56737205.0
company_size 05
company_size_name DEMAIS
national_responsible_entity None
id_head_branch 1
head_branch_name MATRIZ
trade_name None
id_registration_status 2
registration_status_name ATIVA
registration_status_date None
id_registration_status_reason 0
reason_name SEM MOTIVO
foreign_city_name None
id_country None
country_name None
start_activity_date 2004-08-13
cnae 4621400
cnae_name Comércio atacadista de café em grão
cnae_secondary 0121101,0134200,1081301,1081302,1082100,109370...
address_type RODOVIA
address_street BR-491
address_number 2005
address_complement LETRA C
address_neighbourhood VILA TEIXEIRA
postal_code 37132411
id_city 4031
city_name ALFENAS
municipality 3101607
id_state 31
state MG
phone_1_area_code 13
phone_1 32135200
phone_2_area_code None
phone_2 None
fax_area_code 13
fax 32135204
email tax.br@olamnet.com
special_status None
special_status_date None
created_at 2023-11-10 02:32:32.206897
percentage 27.269491
volume_raw 659742355.474752
Primary CNAE:

  cnae_code                description
0   4621400  Wholesale of coffee beans

Secondary CNAEs:

   cnae_code                                                                                        description
0    0121101                                                                    Horticulture, except strawberry
1    0134200                                                                                 Coffee cultivation
2    1081301                                                                                  Coffee processing
3    1081302                                                                       Coffee roasting and grinding
4    1082100                                                               Manufacture of coffee-based products
5    1093701                                                        Manufacture of cocoa and chocolate products
6    4611700  Commercial representatives and agents for the sale of agricultural raw materials and live animals
7    4622200                                                                                 Soybean wholesaler
8    4623103                                                                                  Cotton wholesaler
9    4623105                                                                                   Cacao wholesaler
10   4623106                                                    Wholesale of seeds, flowers, plants and grasses
11   4623199                                        Wholesale of agricultural raw materials not specified above
12   4631100                                                               Wholesale of milk and dairy products
13   4632001                                                          Wholesale of processed grains and legumes
14   4637102                                                                                   Sugar wholesaler
15   4637199                                 Wholesaler specializing in other food products not specified above
16   4683400                                         Wholesale of pesticides, fertilizers and soil conditioners
17   4684299                                Wholesale of other chemicals and petrochemicals not specified above
18   6463800                                                  Other holding companies, except holding companies
19   7490103                        Agronomy and consultancy services for agricultural and livestock activities

Focus on CNPJ 03902252000102
────────────────────────────────────────────────────────────────────────────────
Responsible for 22% of 2022 OLAM export volume, or 5,300,000 tn

Here is how SEI-PCS locates the logistics hub for this CNPJ:

png

Here is what information we have in the database:
1
cnpj 03902252000102
id 35360654
company_name OLAM BRASIL LTDA
juridic_nature 2062
nature_name Sociedade Empresária Limitada
company_social_capital 79183473.0
company_size 05
company_size_name DEMAIS
national_responsible_entity None
id_head_branch 1
head_branch_name MATRIZ
trade_name None
id_registration_status 2
registration_status_name ATIVA
registration_status_date 2004-12-24
id_registration_status_reason 0
reason_name SEM MOTIVO
foreign_city_name None
id_country None
country_name None
start_activity_date 2000-06-21
cnae 4689399
cnae_name Comércio atacadista especializado em outros pr...
cnae_secondary 4622200,4623103,4623199,4631100,4649499,466999...
address_type RUA
address_street RUA PROJETADA - ROD ES 137
address_number 350
address_complement SALA 01
address_neighbourhood COQUEIRAL
postal_code 29830000
id_city 5677
city_name NOVA VENECIA
municipality 3203908
id_state 32
state ES
phone_1_area_code 35
phone_1 32992550
phone_2_area_code 35
phone_2 92730000
fax_area_code 11
fax 38496413
email taxcoe.br@olamagri.com
special_status None
special_status_date None
created_at 2023-11-10 02:32:32.206897
percentage 21.738788
volume_raw 525935713.522766
Primary CNAE:

  cnae_code                                                                 description
0   4689399  Wholesaler specializing in other intermediate products not specified above

Secondary CNAEs:

  cnae_code                                                                                                                        description
0   4622200                                                                                                                 Soybean wholesaler
1   4623103                                                                                                                  Cotton wholesaler
2   4623199                                                                        Wholesale of agricultural raw materials not specified above
3   4631100                                                                                               Wholesale of milk and dairy products
4   4649499                                       Wholesale of other equipment and articles for personal and household use not specified above
5   4669999                                                   Wholesale of other machinery and equipment not specified above; parts and pieces
6   4671100                                                                                             Wholesale of wood and derived products
7   4681801  Wholesale of alcohol, fuel, biodiesel, gasoline and other petroleum products, except lubricants, not carried out by a conveyor of
8   5250805                                                                                                Multimodal Transport Operator - OTM

Focus on CNPJ 07028528004539
────────────────────────────────────────────────────────────────────────────────
Responsible for 19% of 2022 OLAM export volume, or 4,700,000 tn

Here is how SEI-PCS locates the logistics hub for this CNPJ:

png

Here is what information we have in the database:
2
cnpj 07028528004539
id 40612302
company_name OLAM AGRICOLA LTDA.
juridic_nature 2062
nature_name Sociedade Empresária Limitada
company_social_capital 56737205.0
company_size 05
company_size_name DEMAIS
national_responsible_entity None
id_head_branch 2
head_branch_name FILIAL
trade_name None
id_registration_status 8
registration_status_name BAIXADA
registration_status_date 2020-09-30
id_registration_status_reason 1
reason_name EXTINCAO POR ENCERRAMENTO LIQUIDACAO VOLUNTARIA
foreign_city_name None
id_country None
country_name None
start_activity_date 2017-06-13
cnae 4621400
cnae_name Comércio atacadista de café em grão
cnae_secondary 4622200,4623103,4623199,4637102
address_type RUA
address_street ENGENHEIRO CARLOS FIRMO SCHMIDT ROVER (DIRG)
address_number 5015
address_complement SALA 4
address_neighbourhood ZONA PORTUARIA
postal_code 96204470
id_city 8815
city_name RIO GRANDE
municipality 4315602
id_state 43
state RS
phone_1_area_code 13
phone_1 32135200
phone_2_area_code None
phone_2 None
fax_area_code None
fax None
email fiscal.santos@olamnet.com
special_status None
special_status_date None
created_at 2023-11-10 02:32:32.206897
percentage 19.44504
volume_raw 470442090.640714
Primary CNAE:

  cnae_code                description
0   4621400  Wholesale of coffee beans

Secondary CNAEs:

  cnae_code                                                  description
0   4622200                                           Soybean wholesaler
1   4623103                                            Cotton wholesaler
2   4623199  Wholesale of agricultural raw materials not specified above
3   4637102                                             Sugar wholesaler

Focus on CNPJ 03902252001095
────────────────────────────────────────────────────────────────────────────────
Responsible for 12% of 2022 OLAM export volume, or 3,000,000 tn

Here is how SEI-PCS locates the logistics hub for this CNPJ:

png

Here is what information we have in the database:
3
cnpj 03902252001095
id 35354050
company_name OLAM BRASIL LTDA
juridic_nature 2062
nature_name Sociedade Empresária Limitada
company_social_capital 79183473.0
company_size 05
company_size_name DEMAIS
national_responsible_entity None
id_head_branch 2
head_branch_name FILIAL
trade_name None
id_registration_status 2
registration_status_name ATIVA
registration_status_date 2004-10-15
id_registration_status_reason 0
reason_name SEM MOTIVO
foreign_city_name None
id_country None
country_name None
start_activity_date 2004-10-15
cnae 4623199
cnae_name Comércio atacadista de matérias-primas agrícol...
cnae_secondary 4649499,5250805,4622200,4671100,4623103
address_type RUA
address_street FUNCHAL
address_number 418
address_complement CONJ 901
address_neighbourhood VILA OLIMPIA
postal_code 04551060
id_city 7107
city_name SAO PAULO
municipality 3550308
id_state 35
state SP
phone_1_area_code 11
phone_1 30409060
phone_2_area_code None
phone_2 None
fax_area_code 11
fax 38496888
email taxcoe.br@olamagri.com
special_status None
special_status_date None
created_at 2023-11-10 02:32:32.206897
percentage 12.487018
volume_raw 302103727.511581
Primary CNAE:

  cnae_code                                                  description
0   4623199  Wholesale of agricultural raw materials not specified above

Secondary CNAEs:

  cnae_code                                                                                   description
0   4649499  Wholesale of other equipment and articles for personal and household use not specified above
1   5250805                                                           Multimodal Transport Operator - OTM
2   4622200                                                                            Soybean wholesaler
3   4671100                                                        Wholesale of wood and derived products
4   4623103                                                                             Cotton wholesaler