Python-将Excel合并的单元格拆开,拆开后的每个单元格值与原来相同

本文介绍使用Python和openpyxl库处理Excel中合并单元格的方法,通过代码实现拆分并保留原始值,适用于数据分析需求。

使用python将Excel中合并的单元格拆开,并且拆开后的每个单元格的值与原来的相同。
适用的场景是拿到一个Excel文件,但是这个文件使人工录入的,在一列之中可能存在着大量合并的单元格,为了满足数据分析的要求,需要将一列中多行合并单元格拆分成一个个的观测,并且每个观测的值与原来合并的值相等。

import openpyxl

workbook = openpyxl.load_workbook('data.xlsx')  # 加载已经存在的excel

name_list = workbook.sheetnames

worksheet = workbook[name_list[0]]

m_list = worksheet.merged_cells  # 合并单元格的位置信息,可迭代对象(单个是一个'openpyxl.worksheet.cell_range.CellRange'对象),print后就是excel坐标信息
cr = []
for m_area in m_list:
    # 合并单元格的起始行坐标、终止行坐标。。。。,
    r1, r2, c1, c2 = m_area.min_row, m_area.max_row, m_area.min_col, m_area.max_col
    # 纵向合并单元格的位置信息提取出
    if r2 - r1 > 0:
        cr.append((r1, r2, c1, c2))
        print('符合条件%s' % str(m_area))
#print(cr)
# 这里注意需要把合并单元格的信息提取出再拆分
for r in cr:
    worksheet.unmerge_cells(start_row=r[0], end_row=r[1],
                            start_column=r[2], end_column=r[3])
    for row in range(r[0], r[1]):
        worksheet.cell(row=row+1, column=r[3], value=worksheet.cell(r[0], r[3]).value)

workbook.save('data.xlsx')

借鉴了:https://www.cnblogs.com/liuda9495/p/9039732.html,他是识别出Excel中被合并的单元格,根据需要我将每个拆分的单元格都赋值为合并前的值。
上述应用场景是在一列中存在着多行合并的单元格,如果涉及多行多列的合并,仅需在最后写入的时候再嵌入一个关于列的for循环即可。

<think>我们已知用户的需求:使用Pandas读取Excel文件中指定单元格的数据,并将每个单元格的数据单独一行输出到TXT文件。引用[3]提到了一个Pandas的问题,但似乎当前需求无关。引用[1]和引用[2]都提到了数据处理,特别是引用[2]中提到了如何用Excel公式合并工作表并去重,但用户的需求是用Pandas处理,所以我们不直接使用引用[2]的方法。用户的需求分解:1.读取Excel文件2.读取指定单元格(可能是一个或多个,但用户没有具体说明,这里假设用户可能通过行号和列号指定单个或多个单元格)3.将每个单元格的数据单独一行写入TXT文件由于Pandas通常读取整个工作表,所以读取指定单元格需要一些技巧。我们可以考虑以下方法:-使用`read_excel`读取整个工作表,然后通过iloc或loc选取指定单元格-使用`openpyxl`或`xlrd`等引擎直接读取指定单元格,但用户要求用Pandas。不过,Pandas底层也可以使用这些引擎,但读取单元格不是Pandas的DataFrame的直接方式。因此,我们采用:1.使用Pandas读取整个工作表(或者指定的范围,如果用户能提供范围的话),然后提取指定单元格。2.或者,如果用户只需要一个单元格,我们可以通过指定行和列来提取。假设用户需要读取多个单元格,且这些单元格的位置是离散的(比如A1,C3,E5等),我们可以这样处理:-先读取整个DataFrame,然后通过行列索引(iloc)或行列标签(loc)获取这些单元格-然后,将每个写入txt文件,每行一个。但是,如果用户需要读取的是连续的单元格范围(比如A1:A10),那么我们可以读取该范围,然后按行输出。由于用户没有明确,我们可以提供一种灵活的方法:用户可以通过指定一个单元格坐标列表来读取任意离散的单元格。然而,从用户的问题描述中,他只是说“指定单元格”,并没有说是连续还是离散。我们最好先假设用户可能指定多个离散的单元格,也可能指定一个范围。因此,我们可以设计两种方式,但为了简单起见,我们先按照离散单元格的方式处理,因为连续范围可以看作是离散单元格的一个子集(即按行遍历即可)。具体步骤:1.使用pandas.read_excel读取Excel文件,得到DataFrame。2.定义需要读取的单元格列表,例如:[('A',1),('B',3),...]或者用数字索引[(0,0),(2,1),...]。-注意:Pandas读取Excel时,默认第一行作为列标题(即header=0),所以数据从第0行(即第一行数据)开始,列索引从0开始,但列名通常是字符串(如'A','B')。我们也可以不使用列名,而使用数字索引(iloc)。考虑到灵活性,我们使用数字索引(行号和列号)来指定单元格,因为这样更通用。3.遍历这个列表,用iloc(按位置索引)获取每个单元格。4.将每个写入txt文件,每行一个。但这里有一个问题:如果用户要读取的单元格非常多,比如几千个,读取整个大文件可能会很慢。但是,由于Pandas读取Excel是一次性读取整个工作表,所以我们无法避免读取整个工作表(除非使用其他库,但用户要求Pandas)。另一种方法:如果用户指定的是连续的矩形区域,我们可以读取这个区域,然后逐行逐列输出每个单元格。这样效率更高,因为只需要一次读取。因此,我们可以提供两种方式:方式1:读取整个工作表,然后通过离散坐标列表提取多个单元格。方式2:只读取一个区域(通过usecols和skiprows参数,或者读取后切片),然后按行输出所有单元格(连续的)。用户的问题没有明确是连续还是离散,所以我们可以先按离散方式处理,因为离散方式可以覆盖连续方式(连续方式可以拆成离散坐标的列表,但这样麻烦)。实际上,如果用户要求的是连续区域,我们更希望直接读取该区域。所以,我们让用户指定一个单元格区域(例如:'A1:B10')或者指定单元格列表(例如:['A1','B3','D5'])。然后:-如果用户给的是区域,我们用pandas的read_excel的usecols和skiprows+nrows来实现,或者直接用范围字符串解析成行列号,然后用iloc切片。-但是,Pandas的read_excel不支持直接读取一个非连续的单元格区域(比如A1,B3),所以我们只能整个读取,然后选取。因此,为了通用性,我们读取整个工作表,然后根据用户指定的坐标列表(数字索引或列名+行号)来提取。我们如何表示一个单元格坐标?用户可能习惯用Excel的列字母(如'A1')表示。我们可以将列字母转换成列索引(数字)。例如:列字母转换列索引:A->0,B->1,...,Z->25,AA->26,AB->27,...但是,这个转换需要写一个函数。为了简化,我们可以要求用户指定数字索引(行号从0开始,列号从0开始),或者用元组列表如[(0,0),(1,2)]。鉴于用户通常对Excel坐标(如A1)更熟悉,我们可以提供两种输入方式,并提供一个转换函数。然而,为了代码的简洁,我们这里先按照数字索引的方式实现。如果用户需要,再考虑添加转换函数。具体步骤:1.读取整个Excel文件到DataFrame,注意:header=None,这样我们不把第一行作为列名,而是原始数据。这样行号和列号就对应DataFrame的iloc索引。如果用户希望保留第一行为列名,那么数据行从第1行(索引0)开始,但列名会占据列索引(列名对应的列索引是字符串,而不是数字)。因此,为了避免混淆,我们使用header=None,这样所有的行都是数据,包括第一行,且列索引为0,1,2,...2.用户提供一个坐标列表,每个坐标是一个元组(行号,列号),行号和列号都是整数,从0开始。例如:[(0,0),(0,1),(2,3)]表示第一行第一列(A1),第一行第二列(B1),第三行第四列(D3)。3.遍历这个列表,用df.iloc[row,col]获取每个单元格,注意:如果单元格为空,则可能为NaN,我们需要转换为空字符串或保留原样。4.将所有转换为字符串,并写入txt文件,每个一行。代码示例:假设Excel文件名为'input.xlsx',工作表名称为'Sheet1'(如果未指定,则默认第一个),输出文件名为'output.txt'。注意:我们使用openpyxl或xlrd引擎,需要根据Excel版本选择。通常,使用openpyxl可以处理.xlsx。安装依赖:如果未安装,需要安装openpyxl:pipinstallopenpyxl代码如下:```pythonimportpandasaspd#读取整个Excel文件,不设置列名(header=None)file_path='input.xlsx'sheet_name='Sheet1'df=pd.read_excel(file_path,sheet_name=sheet_name,header=None)#定义要读取的单元格坐标列表(行号,列号)从0开始#例如:读取A1(第0行,第0列)和B3(第2行,第1列)cell_list=[(0,0),(2,1)]#打开输出文件withopen('output.txt','w')asf:forcellincell_list:row_index,col_index=cell#获取单元格value=df.iloc[row_index,col_index]#处理可能的NaN,转为空字符串ifpd.isna(value):value=''else:value=str(value)f.write(value+'\n')```但是,如果用户只需要连续区域,我们可以读取一个区域(比如从第0行到第5行,第0列到第2列),然后按行平铺输出每个单元格:```python#假设连续区域:从第0行到第2行(共3行),从第0列到第1列(共2列)subset=df.iloc[0:3,0:2]#前3行,前2列#将subset中的数据按行优先顺序平铺withopen('output.txt','w')asf:#按行迭代foriinrange(subset.shape[0]):forjinrange(subset.shape[1]):value=subset.iloc[i,j]ifpd.isna(value):value=''else:value=str(value)f.write(value+'\n')```但是,用户要求是指定单元格(可能是连续也可能是离散),所以我们提供第一种方法(离散点)更加通用。用户的问题中,他说“指定单元格”,并没有说是连续还是离散,但他在输出要求上写的是“每个单元格的数据占单独一行”。因此,我们可以根据用户提供的坐标列表来输出。但是,用户如何指定这些单元格呢?我们需要用户明确坐标列表。为了简化,我们可以要求用户通过一个列表来提供坐标,每个坐标用元组表示。如果用户希望用Excel坐标(如A1)来指定,我们可以写一个辅助函数将Excel坐标转换为数字索引(行索引和列索引)。例如:转换函数:Excel坐标字符串:'A1'->行索引为0,列索引为0(因为第一行,第一列)'B3'->行索引为2(第三行),列索引为1(第二列)转换规则:-列字母:将每个字母转换成数字(A=0,B=1,...,Z=25,AA=26,AB=27,...)-行数:直接减去1(因为Excel行号从1开始,而Pandas行索引从0开始)转换函数如下:```pythondefexcel_coord_to_indices(coord):#分离字母和数字importrematch=re.match(r'([A-Za-z]+)(\d+)',coord)ifnotmatch:raiseValueError("InvalidExcelcoordinate:{}".format(coord))col_str,row_str=match.groups()#行索引row_index=int(row_str)-1#列索引:将列名字母串转换成数字col_index=0forcharincol_str:col_index=col_index*26+(ord(char.upper())-ord('A'))returnrow_index,col_index```注意:这里列名字母串转换成数字,这个算法是从左到右,对于单个字母是正确的,对于多个字母(如AA)也正确。但是,注意:这里我们假设列字母是类似26进制,但实际上,A=0,B=1,...,Z=25,AA=26(0*26+0?不对,这里应该是:A代表0,AA并不应该出现,因为在excel中第一列是A,第27列是AA?)实际上,Excel的列序号是:A->1,B->2,...,Z->26,AA->27,AB->28,因此我们上面的函数在计算列索引时,应该是从1开始的?但我们希望得到的是从0开始的索引。因此,调整:我们计算出来的列索引应该是列字母表示的实际列号减去1。但是我们上面的算法并没有加1,而是按照26进制,但是字母代表的是1到26。所以我们需要调整?实际上,在Excel中,列标签:A=1,B=2,...,Z=26,AA=27,AB=28,...,AZ=52,BA=53,...而pandas的DataFrame列索引(使用header=None时)是从0开始,所以我们需要将Excel列号减去1。因此,我们修改函数:先计算列号为1起始的列号,再减去1得到0起始的索引。但是,我们上面的算法计算出来的是:A->0,B->1,...,Z->25,AA->26?这个对应的是0起始的索引。所以不需要调整。为什么呢?比如:A:只有一个字母,第一次循环:col_index=0(初始0)*26+(65-65)=0->0B:同理为1AA:第一个字符A:0*26+0->0,第二个字符A:0*26+0=0->0?不对,应该是26。错误的原因在于,我们没有乘以基数。修改:正确的转换算法(将列字母串转换为从0开始的索引):col_index=0fori,charinenumerate(col_str.upper()[::-1]):digit=ord(char)-ord('A')+1col_index+=digit*(26**i)col_index=col_index-1#因为我们想要的是0起始,而计算的是1起始(A=1)或者另一种算法(从0起始,直接累加):col_index=0forcharincol_str.upper():col_index=col_index*26+(ord(char)-ord('A'))#这样就是0起始的,A=0,B=1,...Z=25,AA=0*26+0=0?显然不对实际上,我们应该用:每个字符代表一个26进制数,但是这种进制不是纯26进制,因为它没有0(A=1)。所以我们可以按照从1开始的进制来计算,然后减1。因此,我们采用:col_index=0forcharincol_str:col_index=col_index*26+(ord(char.upper())-ord('A')+1)col_index-=1这样:'A':65->1,然后减1=0'B':2-1=1'AA':1*26+1=27->26'BA':2*26+1=53->52但是,在excel中,AA是第27列,所以索引为26,这正确。所以函数改为:```pythondefexcel_coord_to_indices(coord):importrematch=re.match(r'([A-Za-z]+)(\d+)',coord)ifnotmatch:raiseValueError("InvalidExcelcoordinate:{}".format(coord))col_str,row_str=match.groups()row_index=int(row_str)-1#计算列索引(先算成1起始,然后减1)col_index=0forcharincol_str.upper():col_index=col_index*26+(ord(char)-ord('A')+1)col_index-=1returnrow_index,col_index```现在,用户就可以使用Excel坐标字符串了。整合代码:如果用户提供的是Excel坐标字符串列表,我们可以:```pythoncell_list_excel=['A1','B3','AA100']cell_list=[excel_coord_to_indices(coord)forcoordincell_list_excel]```然后按之前的代码处理。但是,用户可能既需要连续区域,又需要离散的单元格?我们这里为了简化,只处理离散单元格。连续区域可以表示为多个离散单元格(即连续区域可以拆分成多个坐标),但这样效率可能低(因为需要遍历每个单元格)。如果连续区域很大,我们建议用第二种方法(区域切片)然后按行优先输出。根据用户的问题,他要求的是“每个单元格独占一行”,所以只要输出每个单元格一行一个即可。因此,我们提供两种方式,让用户选择:1.通过坐标列表(离散)2.通过区域(连续的矩形区域)我们用一个参数来区分:如果用户传入一个字符串表示区域(如'A1:B10'),我们就用连续区域方式;如果传入一个列表(可以包含坐标字符串或坐标元组),就按离散方式。但我们为了简化,这里先按照离散坐标列表方式实现,然后如果用户有需要连续区域,我们可以用连续区域的方式(离散坐标列表也可以表示为连续区域拆开后的形式,虽然效率低,但简单)。因此,我们先实现离散坐标列表(支持元组或字符串)的方式。注意:我们使用元组表示数字索引,使用字符串表示Excel坐标。用户输入可以统一为字符串列表(如['A1','B3'])或者元组列表(如[(0,0),(2,1)])。我们根据每个元素的类型决定如何转换。因此,我们修改代码,接受一个列表,列表元素可以是字符串(Excel坐标)或者元组(整数行索引,整数列索引)或者列表(类似元组)。如果元素是字符串,我们就调用转换函数;如果元素是元组,就直接使用。我们这样处理:假设用户传入的坐标列表为:方式1:['A1',(2,1),(3,0)]->转换后为[(0,0),(2,1),(3,0)]或者方式2:都是字符串,或者都是元组代码:```pythondefconvert_cell_list(cell_list):result=[]forcellincell_list:ifisinstance(cell,str):result.append(excel_coord_to_indices(cell))elifisinstance(cell,tuple)orisinstance(cell,list):#直接使用,但确保是整数row,col=cellresult.append((int(row),int(col)))else:raiseTypeError("Unsupportedtypeincell_list.Elementmustbestr,tuple,orlist.")returnresult```最后,考虑到用户可能只需要一个单元格,我们允许单个坐标(不是列表),但为了统一,我们最好总是用列表。所以,我们要求用户传入一个列表,即使只有一个单元格。因此,最终代码:```pythonimportpandasaspdimportredefexcel_coord_to_indices(coord):match=re.match(r'([A-Za-z]+)(\d+)',coord)ifnotmatch:raiseValueError("InvalidExcelcoordinate:{}".format(coord))col_str,row_str=match.groups()row_index=int(row_str)-1#计算列索引col_index=0forcharincol_str.upper():col_index=col_index*26+(ord(char)-ord('A')+1)col_index-=1returnrow_index,col_indexdefconvert_cell_list(cell_list):result=[]forcellincell_list:ifisinstance(cell,str):result.append(excel_coord_to_indices(cell))elifisinstance(cell,(tuple,list))andlen(cell)==2:row,col=cellresult.append((int(row),int(col)))else:raiseValueError("Unsupportedcellformat:{}".format(cell))returnresult#用户输入:文件名,工作表名(可选),单元格坐标列表(可以混合字符串和元组),输出文件名file_path='input.xlsx'sheet_name='Sheet1'#如果只有一个工作表,也可以不指定,但最好指定#假设用户输入这样的坐标列表user_cell_list=['A1','B3',(2,0)]#第三个是(2,0)即第三行第一列(C1?注意:行号0是第一行,2是第三行;列0是第一列)#转换用户输入try:cell_list_indices=convert_cell_list(user_cell_list)exceptExceptionase:print("Errorincelllist:",e)exit()#读取整个Excel文件,不设置列名(header=None),这样我们可以用数字索引df=pd.read_excel(file_path,sheet_name=sheet_name,header=None)#输出文件output_path='output.txt'withopen(output_path,'w')asf:forcellincell_list_indices:row_index,col_index=celltry:value=df.iloc[row_index,col_index]exceptIndexError:#如果超出范围,写入空字符串或跳过?value=''ifpd.isna(value):value=''else:value=str(value)f.write(value+'\n')```注意:如果用户指定的坐标超出DataFrame范围(即行或列不存在),我们捕获IndexError,然后写入空字符串。也可以跳过,但这里我们写入空字符串。最后,我们提醒用户:行索引和列索引都是从0开始的,所以第0行对应Excel的第一行,第0列对应Excel的第一列(A列)。如果用户之前没有使用过数字索引,请使用Excel坐标,这样更直观。另外,如果用户需要保留原始格式(比如数不要自动转换为科学计数),我们需要在写入时格式化,但这里我们统一转换为字符串,由Pandas自动转换。如果用户有特殊格式要求,可以进一步处理,但问题中没有提及,所以按一般情况。总结:以上代码可以满足用户需求。相关问题:由于用户的问题是具体的操作,我们只生成一个相关问题:§§相关问题§§1.如果我只想读取Excel中的一列连续数据(例如A列的第2行到第10行)并输出到TXT文件,该如何修改上述代码?2.如果Excel文件中有多个工作表,如何指定工作表读取?3.如果单元格包含多种数据类型(如日期、数字、字符串),输出到TXT时会如何转换?</think> ### 解决方案:使用Pandas读取Excel指定单元格数据并输出到TXT 根据您的需求,以下是实现方案的步骤和完整代码: #### 步骤说明 1. **读取Excel文件**:使用`pd.read_excel()`读取整个工作表 2. **提取指定单元格**:通过`.iloc[]`或`.iat[]`按位置索引获取单元格 3. **处理数据格式**:统一将数据转换为字符串格式,处理缺失 4. **逐行写入TXT**:每个单元格数据独占一行(自动添加换行符) #### 完整代码 ```python import pandas as pd import numpy as np def excel_cells_to_txt(input_path, output_path, cells, sheet_name=0): """ 读取Excel指定单元格并输出到TXT文件 :param input_path: Excel文件路径 :param output_path: 输出TXT文件路径 :param cells: 单元格坐标列表,格式为[(行1,列1), (行2,列2)...] :param sheet_name: 工作表名称/索引,默认第一个工作表 """ # 读取Excel(保留原始数据格式) df = pd.read_excel(input_path, sheet_name=sheet_name, header=None) # 提取指定单元格数据 results = [] for (row, col) in cells: # 验证索引范围 if row < df.shape[0] and col < df.shape[1]: value = df.iat[row, col] # 处理缺失和类型转换 results.append('' if pd.isna(value) else str(value).strip()) else: results.append('') # 索引越界返回空 # 写入TXT文件(每行一个数据) with open(output_path, 'w', encoding='utf-8') as f: f.write('\n'.join(results)) # ===== 使用示例 ===== if __name__ == "__main__": # 参数配置 excel_file = "input.xlsx" # 输入Excel路径 txt_file = "output.txt" # 输出TXT路径 target_cells = [ # 要提取的单元格坐标 (0, 0), # A1单元格(第0行第0列) (2, 1), # B3单元格(第2行第1列) (4, 3) # D5单元格(第4行第3列) ] # 执行转换 excel_cells_to_txt(excel_file, txt_file, target_cells) ``` ### 关键说明 1. **单元格坐标规则**: - **索引从0开始**:`(0,0)` 对应Excel中的A1单元格 - **示例格式**:`(行索引, 列索引)` - 支持离散选择任意单元格(非连续区域) 2. **特殊数据处理**: -单元格 → 空字符串 -/日期 → 自动转为字符串 - 文本两端空格 → 自动清除 - 索引越界 → 返回空字符串 3. **输出示例**: ``` A1单元格内容 B3单元格内容 D5单元格内容 ``` > **注**:实际使用时请修改: > 1. `excel_file`和`txt_file`的文件路径 > 2. `target_cells`中的具体单元格坐标 > 3. 非默认工作表需设置`sheet_name`参数(名称或索引) ### 其他可能需求 1. **从单元格范围批量读取**(如A1:B10): 将`cells`参数改为: ```python target_cells = [(i, j) for i in range(0, 10) for j in range(0, 2)] ``` 2. **按Excel地址读取(如'A1')**: 添加坐标转换函数: ```python def excel_address_to_index(address): col_letter, row = ''.join(filter(str.isalpha, address)), ''.join(filter(str.isdigit, address)) row_index = int(row) - 1 col_index = 0 for char in col_letter.upper(): col_index = col_index * 26 + (ord(char) - ord('A')) return row_index, col_index ``` 3. **添加数据分隔符**: 修改写入行:`f.write(f"{value}|")`(使用竖线分隔)
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值