Distributed Pandas Processing Based on MaxFrame

Updated at:
Copy as MD

MaxFrame is a distributed data processing framework for MaxCompute that provides Pandas-compatible APIs. Instead of rewriting your data analysis code, you can use familiar Pandas operations and let MaxFrame automatically distribute the computation across a MaxCompute cluster. This approach delivers performance that is dozens of times faster than open-source Pandas, while keeping your code nearly identical to standard Pandas workflows.

This topic walks through three common data analysis scenarios using MaxFrame, demonstrating operations such as merge, groupby, agg, drop_duplicates, and sort_values.

Prerequisites

MaxFrame has been installed. For more information, see Preparations.

Common setup

Every scenario in this topic requires an ODPS connection and a MaxFrame session. The following code block shows the shared setup used throughout. Each scenario section includes the full runnable script, but the connection parameters are the same.

from odps import ODPS
from maxframe.session import new_session
import maxframe.dataframe as md
import os

o = ODPS(
    # Set the environment variables ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET
    # to your AccessKey ID and AccessKey secret respectively.
    # We recommend that you do not directly use AccessKey ID and AccessKey secret strings in your code.
    os.getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'),
    os.getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),
    project='your-default-project',
    endpoint='your-endpoint',
)

session = new_session(o)
# The session ID is a string used to associate MaxFrame tasks,
# which is important for debugging and tracking task status.

Parameters:

  • ALIBABA_CLOUD_ACCESS_KEY_ID: To access the target MaxCompute project, you must set this environment variable with an AccessKey ID that has MaxCompute permissions. You can retrieve the AccessKey ID from the AccessKey Management page.

  • ALIBABA_CLOUD_ACCESS_KEY_SECRET: Set this environment variable to the AccessKey secret that corresponds to the AccessKey ID.

  • your-default-project: The name of the MaxCompute project. To view the project name, log on to the MaxCompute console, and choose Workspace > Projects from the left-side navigation pane.

  • your-endpoint: The endpoint for the MaxCompute project's region. Choose the endpoint based on your network connectivity method, for example, http://service.cn-chengdu.maxcompute.aliyun.com/api. For more information, see Endpoints.

Note

MaxFrame uses lazy execution: computations run only when .execute() is called. All processing takes place in the MaxCompute cluster, avoiding unnecessary data transfer.

Prepare data

Before running the scenarios, create two test tables in your MaxCompute project:

TableSchemaRecords
product_maxframe_demoindex bigint, product_id bigint, product_name string, current_price bigint3 products (Nokia, Apple, Samsung)
sales_maxframe_demoindex bigint, sale_id bigint, product_id bigint, user_id bigint, year bigint, quantity bigint, price bigint6 sales records (2008--2015)

Step 1: Create tables and insert data

Run the following code in a Python environment with MaxFrame installed. The script connects to MaxCompute via the ODPS SDK and populates both tables with sample records.

from odps import ODPS
from odps.df import DataFrame as ODPSDataFrame
from maxframe.session import new_session
import maxframe.dataframe as md
import pandas as pd
import os

o = ODPS(
    # Set the environment variables ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET
    # to your AccessKey ID and AccessKey secret respectively.
    os.getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'),
    os.getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),
    project='your-default-project',
    endpoint='your-endpoint',
)

data_sets = [{
    "table_name": "product",
    "table_schema" : "index bigint, product_id bigint, product_name string, current_price bigint",
    "source_type": "records",
    "records" : [
        [1, 100, 'Nokia', 1000],
        [2, 200, 'Apple', 5000],
        [3, 300, 'Samsung', 9000]
    ],
},
{
    "table_name" : "sales",
    "table_schema" : "index bigint, sale_id bigint, product_id bigint, user_id bigint, year bigint, quantity bigint, price bigint",
    "source_type": "records",
    "records" : [
        [1, 1, 100, 101, 2008, 10, 5000],
        [2, 2, 300, 101, 2009, 7, 4000],
        [3, 4, 100, 102, 2011, 9, 4000],
        [4, 5, 200, 102, 2013, 6, 6000],
        [5, 8, 300, 102, 2015, 10, 9000],
        [6, 9, 100, 102, 2015, 6, 2000]
    ],
    "lifecycle": 5
}]

def prepare_data(o: ODPS, data_sets, suffix="", drop_if_exists=False):
    for index, data in enumerate(data_sets):
        table_name = data.get("table_name")
        table_schema = data.get("table_schema")
        source_type = data.get("source_type")

        if not table_name or not table_schema or not source_type:
            raise ValueError(f"Dataset at index {index} is missing one or more required keys: 'table_name', 'table_schema', or 'source_type'.")

        lifecycle = data.get("lifecycle", 5)
        table_name += suffix

        print(f"Processing {table_name}...")
        if drop_if_exists:
            print(f"Deleting {table_name}...")
            o.delete_table(table_name, if_exists=True)

        o.create_table(name=table_name, table_schema=table_schema, lifecycle=lifecycle, if_not_exists=True)

        if source_type == "local_file":
            file_path = data.get("file")
            if not file_path:
                raise ValueError(f"Dataset at index {index} with source_type 'local_file' is missing the 'file' key.")
            sep = data.get("sep", ",")
            pd_df = pd.read_csv(file_path, sep=sep)
            ODPSDataFrame(pd_df).persist(table_name, drop_table=True)
        elif source_type == 'records':
            records = data.get("records")
            if not records:
                raise ValueError(f"Dataset at index {index} with source_type 'records' is missing the 'records' key.")
            with o.get_table(table_name).open_writer() as writer:
                writer.write(records)
        else:
            raise ValueError(f"Unknown data set source_type: {source_type}")

        print(f"Processed {table_name} Done")

prepare_data(o, data_sets, "_maxframe_demo", True)

Step 2: Verify the data

Run the following SQL queries to confirm that both tables contain the expected records.

-- Query the sales_maxframe_demo table
SELECT * FROM sales_maxframe_demo;

Expected output:

+------------+------------+------------+------------+------------+------------+------------+
| index      | sale_id    | product_id | user_id    | year       | quantity   | price      |
+------------+------------+------------+------------+------------+------------+------------+
| 1          | 1          | 100        | 101        | 2008       | 10         | 5000       |
| 2          | 2          | 300        | 101        | 2009       | 7          | 4000       |
| 3          | 4          | 100        | 102        | 2011       | 9          | 4000       |
| 4          | 5          | 200        | 102        | 2013       | 6          | 6000       |
| 5          | 8          | 300        | 102        | 2015       | 10         | 9000       |
| 6          | 9          | 100        | 102        | 2015       | 6          | 2000       |
+------------+------------+------------+------------+------------+------------+------------+
-- Query the product_maxframe_demo table
SELECT * FROM product_maxframe_demo;

Expected output:

+------------+------------+--------------+---------------+
| index      | product_id | product_name | current_price |
+------------+------------+--------------+---------------+
| 1          | 100        | Nokia        | 1000          |
| 2          | 200        | Apple        | 5000          |
| 3          | 300        | Samsung      | 9000          |
+------------+------------+--------------+---------------+

Use MaxFrame for data analysis

The following three scenarios demonstrate increasingly complex data analysis pipelines. All performance comparisons use the same benchmark dataset: a sales table of 50 million records (1.96 GB) and a product table of 100,000 records (3 MB).

Scenario 1: Merge two tables to retrieve sales details with product names

This scenario demonstrates how to use merge() to join two tables. The goal is to retrieve all sale_id values in the sales_maxframe_demo table along with the corresponding product_name, year, and price for each product.

Pandas operations used: merge(), column selection (df[columns])

Sample code:

from odps import ODPS
from maxframe.session import new_session
import maxframe.dataframe as md
import os

o = ODPS(
    os.getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'),
    os.getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),
    project='your-default-project',
    endpoint='your-endpoint',
)

session = new_session(o)
print(session.session_id)

sales = md.read_odps_table("sales_maxframe_demo", index_col="index")
product = md.read_odps_table("product_maxframe_demo", index_col="product_id")

df = sales.merge(product, left_on="product_id", right_index=True)
df = df[["product_name", "year", "price"]]

print(df.execute().fetch())

# Save the result to a MaxCompute table and destroy the session.
md.to_odps_table(df, "result_df", overwrite=True).execute()

session.destroy()

Output:

index product_name  year  price
1            Nokia  2008   5000
2          Samsung  2009   4000
3            Nokia  2011   4000
4            Apple  2013   6000
5          Samsung  2015   9000
6            Nokia  2015   2000

Performance comparison:

EnvironmentTime (seconds)
Local Pandas (V1.3.5)65.8
MaxFrame22

Scenario 2: Find the first year of sales for each product

This scenario demonstrates how to use groupby() and agg() to find the earliest sales year per product, then use merge() with a multi-index to retrieve the full sales record for that year. The result includes the product ID, first year of sales, quantity, and price.

Pandas operations used: groupby(), agg(), merge() with multi-index, column selection

Sample code:

from odps import ODPS
from maxframe.session import new_session
import maxframe.dataframe as md
import os

o = ODPS(
    os.getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'),
    os.getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),
    project='your-default-project',
    endpoint='your-endpoint',
)

session = new_session(o)
print(session.session_id)

# Aggregate to find the earliest sales year for each product.
min_year_df = md.read_odps_table("sales_maxframe_demo", index_col="index")
min_year_df = min_year_df.groupby('product_id', as_index=False).agg(first_year=('year', 'min'))

# Merge to retrieve the corresponding sales records.
sales = md.read_odps_table("sales_maxframe_demo", index_col=['product_id', 'year'])
result_df = md.merge(sales, min_year_df,
                        left_index=True,
                        right_on=['product_id','first_year'],
                        how='inner')

result_df = result_df[['product_id', 'first_year', 'quantity', 'price']]

print(result_df.execute().fetch())

session.destroy()

Output:

product_id  first_year  quantity  price
100         100        2008        10   5000
300         300        2009         7   4000
200         200        2013         6   6000

Performance comparison:

EnvironmentTime (seconds)
Local Pandas (V1.3.5)186
MaxFrame21

Scenario 3: Identify the highest-spending product for each user

This scenario demonstrates a more complex analysis pipeline that chains multiple operations together. For each user, the goal is to find the product on which they spent the most in total.

Pandas operations used: groupby(), agg(), merge(), drop_duplicates(), sort_values()

Sample code:

from odps import ODPS
from maxframe.session import new_session
import maxframe.dataframe as md
import os

o = ODPS(
    os.getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'),
    os.getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'),
    project='your-default-project',
    endpoint='your-endpoint',
)

session = new_session(o)
print(session.session_id)

sales = md.read_odps_table("sales_maxframe_demo", index_col="index")
product = md.read_odps_table("product_maxframe_demo", index_col="product_id")

sales['total'] = sales['price'] * sales['quantity']

product_cost_df = sales.groupby(['product_id', 'user_id'], as_index=False).agg(user_product_total=('total','sum'))
product_cost_df = product_cost_df.merge(product, left_on="product_id", right_index=True, how='right')

user_cost_df = product_cost_df.groupby('user_id').agg(max_total=('user_product_total', 'max'))
merge_df = product_cost_df.merge(user_cost_df, left_on='user_id', right_index=True)

result_df = merge_df[merge_df['user_product_total'] == merge_df['max_total']][['user_id', 'product_id']].drop_duplicates().sort_values(['user_id'], ascending = [1])

print(result_df.execute().fetch())

session.destroy()

Output:

user_id  product_id
100      101         100
300      102         300

Performance comparison:

EnvironmentTime (seconds)
Local Pandas (V1.3.5)176
MaxFrame85

Performance summary

The following table summarizes the performance gains across all three scenarios.

ScenarioLocal Pandas (V1.3.5)MaxFrameSpeedup
1: Merge two tables65.8s22s~3x
2: First year of sales per product186s21s~9x
3: Highest-spending product per user176s85s~2x

Conclusion

MaxFrame integrates seamlessly with the Pandas API to enable automatic distributed processing on MaxCompute. By offloading computation to the MaxCompute cluster, MaxFrame maintains robust data processing capabilities while significantly enhancing the scale and efficiency of data analysis. As the three scenarios above demonstrate, MaxFrame delivers significant speedups -- from 2x to nearly 9x faster than local Pandas -- while allowing you to write standard Pandas code with minimal changes. This makes MaxFrame particularly well-suited for large-scale data analysis workloads where local Pandas processing becomes impractical due to data volume or computation time.