Distributed Pandas Processing Based on MaxFrame
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.
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:
| Table | Schema | Records |
|---|---|---|
product_maxframe_demo | index bigint, product_id bigint, product_name string, current_price bigint | 3 products (Nokia, Apple, Samsung) |
sales_maxframe_demo | index bigint, sale_id bigint, product_id bigint, user_id bigint, year bigint, quantity bigint, price bigint | 6 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 2000Performance comparison:
| Environment | Time (seconds) |
|---|---|
| Local Pandas (V1.3.5) | 65.8 |
| MaxFrame | 22 |
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 6000Performance comparison:
| Environment | Time (seconds) |
|---|---|
| Local Pandas (V1.3.5) | 186 |
| MaxFrame | 21 |
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 300Performance comparison:
| Environment | Time (seconds) |
|---|---|
| Local Pandas (V1.3.5) | 176 |
| MaxFrame | 85 |
Performance summary
The following table summarizes the performance gains across all three scenarios.
| Scenario | Local Pandas (V1.3.5) | MaxFrame | Speedup |
|---|---|---|---|
| 1: Merge two tables | 65.8s | 22s | ~3x |
| 2: First year of sales per product | 186s | 21s | ~9x |
| 3: Highest-spending product per user | 176s | 85s | ~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.