Pandas合并与连接(merge/join/concat)

多表数据整合技术完全指南

一、概述

在数据分析工作中,多表合并是最常见也最重要的操作之一。真实世界的数据很少完整地存在于一张表中,往往分散在多个数据源。Pandas 提供了三种核心合并机制:mergejoinconcat,它们分别对应不同的应用场景。

merge

SQL风格连接

基于列值匹配

支持inner/left/right/outer

join

索引连接

基于索引匹配

merge的语法糖

concat

轴连接

沿行/列拼接

类似SQL的UNION

核心概念速览

  • merge:最灵活、最常用的合并方式,基于一个或多个键将两个DataFrame的行连接起来,类似SQL的JOIN操作
  • join:merge的简化版本,默认基于索引进行连接,语法更简洁
  • concat:纯粹的拼接操作,沿行方向(增加行)或列方向(增加列)合并数据,不依赖键匹配

二、pd.merge() — SQL风格连接

pd.merge() 是Pandas中最强大、最常用的合并函数。它根据一个或多个键(列名)将两个DataFrame的行连接起来,操作方式与SQL的JOIN完全一致。

2.1 基本用法

# 创建两个示例DataFrame import pandas as pd df1 = pd.DataFrame({ '员工ID': [101, 102, 103, 104], '姓名': ['张三', '李四', '王五', '赵六'], '部门': ['技术', '市场', '技术', '财务'] }) df2 = pd.DataFrame({ '员工ID': [101, 102, 103, 105], '薪资': [15000, 12000, 18000, 9000], '职位': ['工程师', '经理', '高级工程师', '助理'] }) # 默认内连接(inner join) result = pd.merge(df1, df2, on='员工ID') print(result)
员工ID 姓名 部门 薪资 职位 0 101 张三 技术 15000 工程师 1 102 李四 市场 12000 经理 2 103 王五 技术 18000 高级工程师

可以看到,merge 默认使用 inner join,只保留两个表中都有匹配的行(员工ID 104和105被排除)。

三、merge参数详解

3.1 how参数 — 连接方式

how 是merge最重要的参数,控制连接方式,与SQL的JOIN类型一一对应。

how参数 SQL对应 说明
'inner' INNER JOIN 只保留键匹配的行(默认)
'left' LEFT JOIN 保留左表所有行,右表无匹配则填NaN
'right' RIGHT JOIN 保留右表所有行,左表无匹配则填NaN
'outer' FULL OUTER JOIN 保留两个表所有行,无匹配填NaN
# 左连接:保留左表所有行 left_join = pd.merge(df1, df2, on='员工ID', how='left') print(left_join) # 右连接:保留右表所有行 right_join = pd.merge(df1, df2, on='员工ID', how='right') print(right_join) # 外连接:保留所有行 outer_join = pd.merge(df1, df2, on='员工ID', how='outer') print(outer_join)
# left_join 输出: 员工ID 姓名 部门 薪资 职位 0 101 张三 技术 15000 工程师 1 102 李四 市场 12000 经理 2 103 王五 技术 18000 高级工程师 3 104 赵六 财务 NaN NaN # right_join 输出: 员工ID 姓名 部门 薪资 职位 0 101 张三 技术 15000 工程师 1 102 李四 市场 12000 经理 2 103 王五 技术 18000 高级工程师 3 105 NaN NaN 9000 助理 # outer_join 输出: 员工ID 姓名 部门 薪资 职位 0 101 张三 技术 15000 工程师 1 102 李四 市场 12000 经理 2 103 王五 技术 18000 高级工程师 3 104 赵六 财务 NaN NaN 4 105 NaN NaN 9000 助理

理解连接方式

选择策略:

  • inner:只关心两个表都有记录的数据,最常用且效率最高
  • left:以左表为主,补充右表信息(如:员工表 left join 薪资表)
  • right:以右表为主,功能与left对称,通常用left替代
  • outer:需要完整数据视图时使用,注意会产生大量NaN

3.2 on / left_on / right_on — 连接键指定

当两个表用于连接的列名相同时,直接用 on 指定;列名不同时,分别用 left_onright_on

# 列名相同:使用 on pd.merge(df1, df2, on='员工ID') # 列名不同:使用 left_on 和 right_on df2_renamed = df2.rename(columns={'员工ID': '员工编号'}) result = pd.merge(df1, df2_renamed, left_on='员工ID', right_on='员工编号') print(result) # 多键连接:传递列名列表 orders = pd.DataFrame({ '客户ID': [1, 1, 2], '产品ID': ['A', 'B', 'A'], '金额': [100, 200, 150] }) details = pd.DataFrame({ '客户ID': [1, 1, 2], '产品ID': ['A', 'B', 'B'], '产品名': ['手机', '电脑', '平板'] }) # 多键内连接 merged_multi = pd.merge(orders, details, on=['客户ID', '产品ID'], how='inner') print(merged_multi)
# 多键合并结果: 客户ID 产品ID 金额 产品名 0 1 A 100 手机 1 1 B 200 电脑

3.3 suffixes参数 — 重叠列名处理

当两个表有同名的非键列时,merge会自动添加后缀区分,默认后缀为 ('_x', '_y')

# 两个表都有同名的'备注'列 df_a = pd.DataFrame({ 'ID': [1, 2], '名称': ['A', 'B'], '备注': ['来自源A', '来自源A'] }) df_b = pd.DataFrame({ 'ID': [1, 2], '值': [10, 20], '备注': ['来自源B', '来自源B'] }) # 默认后缀 result = pd.merge(df_a, df_b, on='ID') print(result.columns) # Index(['ID', '名称', '备注_x', '值', '备注_y']) # 自定义后缀 result = pd.merge(df_a, df_b, on='ID', suffixes=('_左表', '_右表')) print(result)
ID 名称 备注_左表 值 备注_右表 0 1 A 来自源A 10 来自源B 1 2 B 来自源A 20 来自源B

四、DataFrame.join() — 索引连接

join()merge 基于索引连接的快捷方式。它默认使用两个DataFrame的索引作为连接键,语法更简洁。

# 创建基于索引的数据 left = pd.DataFrame({ '名称': ['苹果', '香蕉', '橙子'] }, index=['A', 'B', 'C']) right = pd.DataFrame({ '价格': [5.5, 3.0, 4.2], '库存': [100, 200, 150] }, index=['A', 'B', 'D']) # 左连接(默认) result = left.join(right) print(result) # 指定连接方式 result_inner = left.join(right, how='inner') print(result_inner)
# 左连接结果: 名称 价格 库存 A 苹果 5.5 100.0 B 香蕉 3.0 200.0 C 橙子 NaN NaN # 内连接结果: 名称 价格 库存 A 苹果 5.5 100 B 香蕉 3.0 200

merge vs join 对比

  • merge:基于列值连接,需要指定 on/left_on/right_on,更灵活
  • join:基于索引连接,语法简洁,适合索引对齐的场景
  • join可混合使用:join也可以按列连接,通过传入 on 参数指定左表的列
  • 本质关系:join是merge的特例,df1.join(df2) 等价于 pd.merge(df1, df2, left_index=True, right_index=True)

join 还有一个非常有用的特性:可以一次性合并多个DataFrame。

# 一次合并多个DataFrame df_a = pd.DataFrame({'A': [1, 2]}, index=['x', 'y']) df_b = pd.DataFrame({'B': [3, 4]}, index=['x', 'y']) df_c = pd.DataFrame({'C': [5, 6]}, index=['x', 'y']) result = df_a.join([df_b, df_c]) print(result)
A B C x 1 3 5 y 2 4 6

五、pd.concat() — 轴连接

concat 用于纯粹的拼接操作,不依赖键匹配。它沿行方向(axis=0)或列方向(axis=1)合并数据。

5.1 行方向连接(axis=0)

按行拼接,相当于SQL的UNION ALL。这是最常用的 concat 方式,用于追加数据。

# 创建两个季度数据 q1 = pd.DataFrame({ '产品': ['A', 'B', 'C'], '销量': [100, 200, 150], '月份': [1, 1, 1] }) q2 = pd.DataFrame({ '产品': ['A', 'B', 'D'], '销量': [130, 180, 90], '月份': [2, 2, 2] }) # 默认axis=0,按行拼接 all_data = pd.concat([q1, q2]) print(all_data)
产品 销量 月份 0 A 100 1 1 B 200 1 2 C 150 1 0 A 130 2 1 B 180 2 2 D 90 2

5.2 ignore_index参数

如上例所示,concat 保留了原DataFrame的索引。使用 ignore_index=True 可以重置为连续整数索引。

# 重置索引 all_data_reset = pd.concat([q1, q2], ignore_index=True) print(all_data_reset) # 输出: # 产品 销量 月份 # 0 A 100 1 # 1 B 200 1 # 2 C 150 1 # 3 A 130 2 # 4 B 180 2 # 5 D 90 2

5.3 keys参数 — 添加层级索引

keys 参数可以为每个源DataFrame添加标签,创建MultiIndex层级索引。

# 添加层级索引标签 result = pd.concat([q1, q2], keys=['Q1', 'Q2']) print(result)
产品 销量 月份 Q1 0 A 100 1 1 B 200 1 2 C 150 1 Q2 0 A 130 2 1 B 180 2 2 D 90 2

5.4 列方向连接(axis=1)

按列拼接,相当于横向合并,通常基于索引对齐。

# 列方向连接 left = pd.DataFrame({ 'ID': [1, 2, 3], '名称': ['X', 'Y', 'Z'] }) right = pd.DataFrame({ '价格': [10.5, 20.0, 15.0], '数量': [50, 30, 40] }) combined = pd.concat([left, right], axis=1) print(combined)
ID 名称 价格 数量 0 1 X 10.5 50 1 2 Y 20.0 30 2 3 Z 15.0 40

5.5 join参数 — 内外连接

concat 默认使用 join='outer',保留所有索引。使用 join='inner' 可以只保留交集。

# outer join(默认):保留所有行 df_a = pd.DataFrame({'A': [1, 2]}, index=['x', 'y']) df_b = pd.DataFrame({'B': [3, 4]}, index=['y', 'z']) outer = pd.concat([df_a, df_b], axis=1) print('outer:') print(outer) # inner join:只保留共同索引 inner = pd.concat([df_a, df_b], axis=1, join='inner') print('inner:') print(inner)
outer: A B x 1.0 NaN y 2.0 3.0 z NaN 4.0 inner: A B y 2 3

六、多对多合并

当两个表的连接键都有重复值时,就会发生多对多合并。这种情况下,会产生笛卡尔积——左表每个匹配行与右表每个匹配行组合。

# 多对多合并示例 left_mm = pd.DataFrame({ '组': ['A', 'A', 'B'], '值_L': [1, 2, 3] }) right_mm = pd.DataFrame({ '组': ['A', 'A', 'B', 'B'], '值_R': [10, 20, 30, 40] }) result = pd.merge(left_mm, right_mm, on='组') print(result)
组 值_L 值_R 0 A 1 10 1 A 1 20 2 A 2 10 3 A 2 20 4 B 3 30 5 B 3 40

注意多对多合并的陷阱

  • 多对多合并会显著增加行数(笛卡尔积),大表操作时需格外注意内存消耗
  • 合并前建议检查连接键的重复情况:df.duplicated(subset=['键']).sum()
  • 如果不需要笛卡尔积,可以先对重复键做聚合去重再进行合并
# 检查连接键重复情况 print('左表组重复数:', left_mm.duplicated(subset=['组']).sum()) print('右表组重复数:', right_mm.duplicated(subset=['组']).sum()) # 先聚合再合并(避免笛卡尔积) left_agg = left_mm.groupby('组')['值_L'].agg(list).reset_index() right_agg = right_mm.groupby('组')['值_R'].agg(list).reset_index() clean_result = pd.merge(left_agg, right_agg, on='组') print(clean_result)

七、索引合并与层级处理

实际工作中,很多DataFrame的索引承载着业务含义(如时间序列、分组标签)。Pandas提供了丰富的索引合并功能。

7.1 left_index / right_index 参数

merge 可以通过 left_indexright_index 参数指定索引作为连接键。

# 左表用列、右表用索引 df_left = pd.DataFrame({ '键': ['a', 'b', 'c'], '值': [1, 2, 3] }) df_right_idx = pd.DataFrame({ '描述': ['Alpha', 'Beta', 'Gamma'] }, index=['a', 'b', 'd']) # left_on + right_index 混合使用 mixed = pd.merge( df_left, df_right_idx, left_on='键', right_index=True, how='left' ) print(mixed)
键 值 描述 0 a 1 Alpha 1 b 2 Beta 2 c 3 NaN

7.2 多重索引合并

当索引是MultiIndex时,合并操作需要特别注意层级匹配。

# 多重索引合并 mi_left = pd.DataFrame({ '得分': [95, 88, 76] }, index=pd.MultiIndex.from_tuples([ ('2024', 'Q1'), ('2024', 'Q2'), ('2024', 'Q3') ], names=['年', '季'])) mi_right = pd.DataFrame({ '目标': [90, 90, 85] }, index=pd.MultiIndex.from_tuples([ ('2024', 'Q1'), ('2024', 'Q2'), ('2024', 'Q4') ], names=['年', '季'])) # 基于多重索引合并 mi_result = mi_left.join(mi_right, how='outer') print(mi_result)
得分 目标 年 季 2024 Q1 95.0 90.0 Q2 88.0 90.0 Q3 76.0 NaN Q4 NaN 85.0

八、重叠列名处理高级技巧

除了 suffixes 参数外,还有几种方式处理合并后的重叠列名问题。

8.1 合并前重命名

# 方法1:合并前手动重命名 df_b_renamed = df_b.rename(columns={'备注': '备注_B'}) result = pd.merge(df_a, df_b_renamed, on='ID') print(result.columns) # 方法2:合并后重命名列 result = pd.merge(df_a, df_b, on='ID', suffixes=('', '_B')) print(result.columns)

8.2 选择性地保留列

# 只选择需要的列,避免冲突 df_b_subset = df_b[['ID', '值']] # 排除'备注'列 result = pd.merge(df_a, df_b_subset, on='ID') print(result) # 合并后删除不需要的重叠列 result = pd.merge(df_a, df_b, on='ID') result.drop(columns=['备注_y'], inplace=True) result.rename(columns={'备注_x': '备注'}, inplace=True) print(result)

九、合并效率优化

当处理大规模数据时,合并效率至关重要。以下是一些实用的优化技巧。

性能优化策略

  • 内连接优先:inner join 不需要处理NaN,计算量最小
  • 减少数据量:合并前先过滤不需要的行和列
  • 设置索引:对连接键设置索引可以加速合并
  • 使用类别类型:将字符串列转为category类型,减少内存占用
  • 分块处理:超大表可以分块读取后合并
# 策略1:合并前过滤 df2_filtered = df2[df2['薪资'] > 10000] result = pd.merge(df1, df2_filtered, on='员工ID') # 策略2:设置索引加速 df1_idx = df1.set_index('员工ID') df2_idx = df2.set_index('员工ID') result = df1_idx.join(df2_idx) # 策略3:类别类型优化 df1['部门'] = df1['部门'].astype('category') # 策略4:大表分块合并 import numpy as np chunks = np.array_split(large_df, 10) results = [] for chunk in chunks: merged_chunk = pd.merge(chunk, lookup_table, on='key') results.append(merged_chunk) final_result = pd.concat(results, ignore_index=True)

十、append()已弃用 vs concat

在早期版本的Pandas中,DataFrame.append() 是常用的行追加方法。但自 Pandas 1.4.0 起,append() 已被标记为弃用(deprecated),并在 Pandas 2.0 中正式移除。官方推荐统一使用 pd.concat()

append() 已移除 — 不要在新代码中使用

如果你使用的是 Pandas 2.0+,直接调用 df.append() 会抛出 AttributeError。所有追加操作都应迁移到 pd.concat()

# 旧写法(已移除,不要使用) # df1.append(df2) # ❌ AttributeError # df1.append([df2, df3]) # ❌ AttributeError # 新写法(推荐) result = pd.concat([df1, df2], ignore_index=True) # 追加多个表 result = pd.concat([df1, df2, df3], ignore_index=True) # 如果只需要追加单行(类似旧的 df.append(row)) new_row = pd.DataFrame([{'产品': 'E', '销量': 300, '月份': 3}]) result = pd.concat([q1, new_row], ignore_index=True) print(result)
产品 销量 月份 0 A 100 1 1 B 200 1 2 C 150 1 3 E 300 3

concat 相比 append 的优势

  • 性能更好:concat 预先分配内存,append 逐次扩展导致多次复制
  • 语义一致:concat 统一了行追加和列拼接,API更简洁一致
  • 功能更强:concat 支持 keys、join 等高级参数
  • 循环中效率:在循环中反复 append 效率极低,应在循环外使用列表收集后用 concat
# 循环中高效追加的最佳实践 # ❌ 错误做法(每次循环都复制DataFrame) result = pd.DataFrame() for chunk in chunks: result = pd.concat([result, chunk]) # 越来越慢! # ✅ 正确做法(先收集再合并) pieces = [] for chunk in chunks: pieces.append(chunk) result = pd.concat(pieces, ignore_index=True)

十一、综合案例

下面通过一个完整的电商数据分析案例,综合运用 merge、join 和 concat 三种合并方式。

案例:电商订单数据分析

场景: 分析某电商平台2025年1-3月的订单数据,计算各品类销售额排名和环比增长。

# ========== 数据准备 ========== import pandas as pd import numpy as np # 1. 用户信息表 users = pd.DataFrame({ 'user_id': [1, 2, 3, 4], 'name': ['Alice', 'Bob', 'Charlie', 'David'], 'city': ['北京', '上海', '广州', '深圳'] }) # 2. 商品信息表 products = pd.DataFrame({ 'product_id': ['P01', 'P02', 'P03', 'P04'], 'product_name': ['手机', '电脑', '耳机', '键盘'], 'category': ['数码', '数码', '配件', '配件'], 'price': [5999, 8999, 399, 299] }) # 3. 各月订单数据 orders_jan = pd.DataFrame({ 'order_id': [1001, 1002, 1003], 'user_id': [1, 2, 1], 'product_id': ['P01', 'P02', 'P03'], 'quantity': [2, 1, 3], 'month': ['2025-01'] * 3 }) orders_feb = pd.DataFrame({ 'order_id': [1004, 1005, 1006, 1007], 'user_id': [2, 3, 4, 1], 'product_id': ['P01', 'P03', 'P04', 'P02'], 'quantity': [1, 2, 1, 1], 'month': ['2025-02'] * 4 }) orders_mar = pd.DataFrame({ 'order_id': [1008, 1009], 'user_id': [3, 1], 'product_id': ['P02', 'P01'], 'quantity': [1, 2], 'month': ['2025-03'] * 2 }) # ========== 第一步:合并所有订单 ========== all_orders = pd.concat( [orders_jan, orders_feb, orders_mar], ignore_index=True ) print('所有订单:') print(all_orders) # ========== 第二步:关联用户信息 ========== orders_with_user = pd.merge( all_orders, users, on='user_id', how='left' ) print('\n关联用户信息:') print(orders_with_user[['order_id', 'name', 'city', 'product_id', 'quantity']]) # ========== 第三步:关联商品信息 ========== orders_full = pd.merge( orders_with_user, products, on='product_id', how='left' ) # 计算订单金额 orders_full['amount'] = orders_full['quantity'] * orders_full['price'] print('\n完整订单明细(含金额):') print(orders_full[['order_id', 'name', 'product_name', 'category', 'quantity', 'amount', 'month']]) # ========== 第四步:各品类月度销售额 ========== category_monthly = orders_full.groupby( ['category', 'month'] )['amount'].sum().reset_index() print('\n各品类月度销售额:') print(category_monthly) # ========== 第五步:计算环比增长 ========== # 将各品类数据拆开计算环比 categories = category_monthly['category'].unique() growth_list = [] for cat in categories: cat_data = category_monthly[category_monthly['category'] == cat] cat_data = cat_data.set_index('month') cat_data['growth'] = cat_data['amount'].pct_change() * 100 growth_list.append(cat_data.reset_index()) # 用concat合并所有品类的环比数据 growth_result = pd.concat(growth_list, ignore_index=True) print('\n各品类销售额环比增长(%):') print(growth_result)
所有订单: order_id user_id product_id quantity month 0 1001 1 P01 2 2025-01 1 1002 2 P02 1 2025-01 2 1003 1 P03 3 2025-01 3 1004 2 P01 1 2025-02 4 1005 3 P03 2 2025-02 5 1006 4 P04 1 2025-02 6 1007 1 P02 1 2025-02 7 1008 3 P02 1 2025-03 8 1009 1 P01 2 2025-03 关联用户信息: order_id name city product_id quantity 0 1001 Alice 北京 P01 2 1 1002 Bob 上海 P02 1 2 1003 Alice 北京 P03 3 3 1004 Bob 上海 P01 1 4 1005 Charlie 广州 P03 2 5 1006 David 深圳 P04 1 6 1007 Alice 北京 P02 1 7 1008 Charlie 广州 P02 1 8 1009 Alice 北京 P01 2 完整订单明细(含金额): order_id name product_name category quantity amount month 0 1001 Alice 手机 数码 2 11998 2025-01 1 1002 Bob 电脑 数码 1 8999 2025-01 2 1003 Alice 耳机 配件 3 1197 2025-01 3 1004 Bob 手机 数码 1 5999 2025-02 4 1005 Charlie 耳机 配件 2 798 2025-02 5 1006 David 键盘 配件 1 299 2025-02 6 1007 Alice 电脑 数码 1 8999 2025-02 7 1008 Charlie 电脑 数码 1 8999 2025-03 8 1009 Alice 手机 数码 2 11998 2025-03 各品类月度销售额: category month amount 0 配件 2025-01 1197 1 配件 2025-02 1097 2 数码 2025-01 20997 3 数码 2025-02 14998 4 数码 2025-03 20997 各品类销售额环比增长(%): category month amount growth 0 配件 2025-01 1197 NaN 1 配件 2025-02 1097 -8.3542 2 数码 2025-01 20997 NaN 3 数码 2025-02 14998 -28.5707 4 数码 2025-03 20997 40.0000

十二、常见错误与排查

常见问题及解决方案

  • KeyError/列名不存在:检查列名是否拼写正确,使用 df.columns.tolist() 查看所有列
  • 合并后行数异常增多:连接键存在重复值导致笛卡尔积,检查重复情况
  • 意外出现NaN:确认连接方式是否正确,检查键值是否完全匹配
  • MemoryError:数据量过大,用分块处理或先过滤再合并
  • 数据类型不一致:int列与float列合并可能引起类型提升,确保类型一致
# 排查工具函数 def inspect_merge(left, right, key): """检查合并前的数据质量""" print(f'左表行数: {len(left)}, 右表行数: {len(right)}') print(f'左表键唯一值数: {left[key].nunique()}') print(f'右表键唯一值数: {right[key].nunique()}') print(f'左表键重复行: {left.duplicated(subset=[key]).sum()}') print(f'右表键重复行: {right.duplicated(subset=[key]).sum()}') # 检查键值类型是否一致 print(f'左表键类型: {left[key].dtype}') print(f'右表键类型: {right[key].dtype}') # 检查键值交集 common_keys = set(left[key]) & set(right[key]) print(f'共同键值数: {len(common_keys)}')

核心要点总结

十三、进一步思考与实践

掌握Pandas的合并操作是数据分析进阶的重要里程碑。在实践中,建议从以下几个方向继续深入:

进阶方向

  • SQL vs Pandas对照学习:将SQL JOIN语句与Pandas merge对照理解,加深对连接语义的把握
  • 掌握 reduce 批量合并functools.reduce 结合 merge 实现多个DataFrame的连续合并
  • 理解数据库范式:学习数据库范式设计,理解为何数据需要拆分存储、合并使用
  • pandas.merge_asof:学习时间序列最近匹配合并,适用于事件时间对齐场景
  • 并行处理:大规模数据合并可考虑 Dask、Polars 等并行计算框架
# functools.reduce 批量合并 from functools import reduce dfs = [df1, df2, df3, df4] result = reduce( lambda left, right: pd.merge(left, right, on='key', how='outer'), dfs ) # merge_asof:时间序列最近匹配 # 适用于将交易事件匹配到最近的行情数据 trades = pd.DataFrame({ 'time': pd.to_datetime(['2025-01-01 09:30:05', '2025-01-01 09:30:15']), 'price': [100.5, 101.0] }) quotes = pd.DataFrame({ 'time': pd.to_datetime(['2025-01-01 09:30:00', '2025-01-01 09:30:10', '2025-01-01 09:30:20']), 'bid': [100.0, 100.3, 100.8], 'ask': [100.6, 100.9, 101.2] }) matched = pd.merge_asof(trades, quotes, on='time', direction='backward') print(matched)
time price time bid ask 0 2025-01-01 09:30:05 100.5 2025-01-01 09:30:00 100.0 100.6 1 2025-01-01 09:30:15 101.0 2025-01-01 09:30:10 100.3 100.9