拉取 开发代码 单元测试
筛选部分代码,函数封装,构造数据执行单元测试
根据需求点 测试点,对开发代码进行封装
测试数据构造弥补 开发联调数据场景或者条件分支不能覆盖的不足
测试数据构造+测试场景单元测试 case化
温故知新:被装走查完成 伊利走查完成 文档复制来源于 D:\Python\SalesForcastTest\crm\思考
1.构造dataframe 通过dict
默认orient ='columns',不要让value为标称属性,把value转换为list对象再传入
test_data = {'company_name': [3, 2, 1, 0], 'col_2': ['a', 'b', 'c', 'd']}
test_data_df = pd.DataFrame.from_dict(test_data)
print(test_data_df)
或者
data = {'name': 'Wang', 'age': 15, 'sex': 'male'}
df = pd.DataFrame.from_dict(data, orient='index').T
print(df)
或者
my_dict = {'i': 1, 'love': 2, 'you': 3}
my_df = pd.DataFrame(my_dict,index=[0]).T
或者
my_dict = {'i': 1, 'love': 2, 'you': 3}
my_list = [my_dict]
my_df = pd.DataFrame(my_list).T
2.插入一行数据 append
若追加的行中存在原数据没有的列,会新增一列,并用nan填充
若追加的行数据中缺少原数据某列,同样以nan填充
DataFrame.append(other, ignore_index=False,...) 语法
other:可以是 series、dataframe、dict、list
ignore_index:默认False,一般使用True(行索引被重设为从0开始的整数索引)
test_data_df = test_data_df.append({}, ignore_index=True) # 插入一行都是NaN的数据
test_data_df = test_data_df.append({"company_name": '', "contacts_name": ''}, ignore_index=True) # 插入一行都是''的数据
如果有多个字段 除了 company_name,contacts_name, 其他字段默认填充为NaN
pandas 1.4 版本中宣布 append() 方法将被弃用,同时在这个版本中会进行警告,
提示使用 pandas.concat()
pandas.concat() 更灵活,它支持 axis,使类似操作更加统一
concat 将是性能最好的,append 太慢
数据合并 列 使用merge 行使用concat append(也可以)
对于无关联字段的df数据按照列合并时,使用concat
#当你横向合并数据时。
pd.concat([df0, df1], axis=1)
# 两者索引不相同时,就会用 NaN 填充 如果希望它们不受索引的影响,可以先重置索引再执行concat连接。
pd.concat([df0.reset_index(drop=True),df2.reset_index(drop=True)],axis=1)
# 横向拓展设置axis=1,内连接指定join='inner'或者外连接指定join='outer'(默认值,会自动填充NAN)
res2 = pd.concat([df, df1], axis=1, join='inner')
res3 = pd.concat([df, df1], axis=1)
2.1 insert 函数 插入列
DataFrame.insert(loc, column, value, allow_duplicates=False) 语法
loc :参数column插入的位置,如果想插入到第一例则为0,取值范围: 0 <= loc <= len(columns),其中len(columns)为Dataframe的列数
column :给 插入数据value取列名,可为数字,字符串等
value :可以是整数,Series或者数组等
allow_duplicates : 默认 False
最后一列插入: value部分要匹配是多少行就是有多少个数据的 list 或者 series
eg:
frame.insert(len(frame.columns), 'list', [x for x in range(5)])
eg:
# 增加 join_columns 字段,命名为'join_columns', 赋值为行索引号
df1.insert(df1.shape[1], 'join_columns', df1.index.tolist())
eg:
把allow_duplicates设置为True,可实现重复值的插入
frame.insert(0, 'num', np.ones(5), allow_duplicates=True)
eg:
#改变某一列的位置。如:先删除gender列,然后在原表data中第0列插入被删掉的列。
data.insert(0, '性别', data.pop('gender'))#pop返回删除的列,插入到第0列,并取新名为'性别'
eg:
#删除gender列,不改变原来的data数据,返回删除后的新表data_2。axis为1表示删除列,0表示删除行。inplace为True表示直接对原表修改。
data_2 = data.drop('gender', axis=1, inplace=False)
3.去除字段前后空格
data_crm['company_name'] = data_crm['company_name'].str.strip()
4.判断 字段的值是否在指定的list中 test_list是常量数组
if any(e in x['company_name'].upper() for e in test_list)
5.获取指定字段去重后的list 按照 多个字段整体去重
duplicated_number = data_crm[data_crm.duplicated(subset=['company_name', 'company_phone_mw'], keep=False)][
'clue_number'].to_list()
6.字段操作,apply+lambda
data_crm['low_quality_reason'] = data_crm.apply(lambda x: x['low_quality_reason'] + ',信息完备性-省市区县为空' if (len(
x['province']) * len(x['city']) * len(x['district'])) == 0 else x['low_quality_reason'], axis=1)
7.声明一个空dataframe 指定列名
test_data_df = pd.DataFrame(columns=["company_name","company_phone_mw","clue_number"])
test_data_df.empty
# True
8.判断指定字段 是否为 NaN (1行数据的时候)
if dfs[['dup_source']].isnull().any().any(): # dfs[['dup_source']] 是一个dataframe
判断某个字段是否全部为nan( df['qty'] 是一个series)
if df['qty'].isnull().all():
np.nan 是可以赋值的:
data2.loc[data2['order_date'].isin(dateList), 'qty'] = np.nan # 将 order_date 字段满足条件的 qty字段全部赋值为np.nan
loc 使用:salesSumByMonthNotNaN = salesSumByMonth.loc[~np.isnan(salesSumByMonth['qty'])]
9.新增一列 指定为NaN
dfs['v'] = np.nan
以前常用的是df.loc[:,'新字段名称'] = XXX
10.取第一行的指定列
this = dfs.iloc[:1][['uuid', col]]
11.指定列 进行fillna
final_df['v_x'] = final_df['v_x'].fillna('')
全量处理时候 直接用 final_df.fillna('') 可以使用参数 value=xxx,inplace=True,指定行或者列 灵活填充 等
# 用前一行的值填补空值
print(d.fillna(method='pad',axis=0))
# 用后一列的值填补空值
print(d.fillna(method='backfill', axis=1))
# 用0填补空值
print(d.fillna(value=0))
实例:直接使用inplace=True 对制定字段填充为目标值
withEventInfluencePredict["final_value"].fillna(value=0.0, inplace=True)
12.字符串拼接时候 不同列的数据格式不一致,需要提前转为str 值为NaN和字符串拼接 结果是NaN nan可以转为’'再进行拼接就可以了
13.根据字段排序 当场生效 可以根据多个字段排序
final_df.sort_values(by=["uuid"], inplace=True)
# 分别指定升序和降序 ascending可以不用,默认升序
df.sort_values(by=["aqiLevel", "bWendu"], ascending=[True, False])
14.判断是否为np.nan
def sort_list(x):
if x is not np.nan: # 判断值是否为nan ==是用作值判断的,而NaN并没有值,is是对象引用判断,np.nan is np.nan,结果就是你要的True
l = x.split(',')
。。。
df["low_quality_reason"] = df["low_quality_reason"].map(sort_list)
15.分组处理常用 apply中指定参数(函数名传参就不说了 f ,dfs实际就是分组之后的对象(可能是个df或者dfGrouper的可以自己type看看))
def f(dfs, col):
pass
df1 = df1.groupby(['uuid', 'company_name'], as_index=False).apply(f, col='company_name')
16.获取pandas df 的第一行
print('-----------')
print(data.iloc[0]) # # Series
print('------第一行-----')
print(data.iloc[:1])
print(type(data.iloc[:1])) # DataFrame
print('------指定列的第一行-----')
print(data['subjects'].iloc[:1]) # DataFrame
print('------第一行-----')
print(data.head(1)) # DataFrame
print('------指定列的第一行-----') # DataFrame
print(data['id'].head(1))
print('------第一行-----')
print(data.loc[data.index[0]]) # Series
print('------第一行-----')
print(data.loc[:data.index[0]]) # DataFrame
-
筛选数据范围,指定字段在list中
hisMontSalesRdc = preprocessRetData.loc[
(preprocessRetData[‘sku_id’].map(str) + preprocessRetData[‘wh_no’].map(str)).isin(eventList)
& (preprocessRetData[‘order_date’] >= ‘2021-03-14’)] -
数据格式 的转换操作
转 float
mergeData.loc[:, ‘effect’] = mergeData[‘effect’].astype(float)在apply使用的函数中有这样实例代码:df[‘qty’] = df[‘qty’].astype(float)
df.loc[:, ‘qty’] = df[‘qty’].map(float) # ok
统一调整字段数据格式
qtys = ['qty', 'bo_process_qty', 'pre_process_qty', 'bo_qty'] for i in qtys: data1[i] = data1[i].astype(float)
转 str
predictMidData.loc[:, ‘qty’] = predictMidData[‘qty’].astype(str) -
字段重命名
mergeData.rename(columns={‘bod_key’: ‘bod_no’, ‘wh_no’: ‘wh’, ‘sku_id’: ‘sku’}, inplace=True)通过list元素顺序 可重新调整df的列顺序
colw = [‘name’,‘id’]
df = data[colw]
print(df)通过list,在已知道全量字段(字段数量)时候,直接改字段名称 newc中可全部修复,也可为更改后的列名
newc = [‘col1’,‘col2’]
df.columns = newc
print(df)更改部分字段名称 使用rename函数(也可全量遍历)
df.rename(columns={‘col1’:‘rename_col1’},inplace=True)
print(df) -
获取 指定条件的索引 根据索引操作数据,使用fillna 将制定字段填充为 目标字段值
indexList = data[((data[‘order_date’] >= data[‘event_start_time’]) & (data[‘order_date’] <= data[‘event_end_time’]))].index.tolist()
data.loc[indexList, ‘qtynew’] = data[‘qty’] + data[‘final_value’]
with pd.option_context(‘mode.chained_assignment’, None):
data[‘qtynew’].fillna(value=data[‘qty’], inplace=True) -
pandas round 使用
所有字段保留两位小数
df.round(2)指定不同字段保留不同小数位数
df.round({‘A’: 1, ‘C’: 2})指定不同字段保留不同小数位数
decimals = pd.Series([1, 0, 2], index=[‘A’, ‘B’, ‘C’])
df.round(decimals) -
数据差集
df_diff1 = pd.concat([df1,df2,df2]).drop_duplicates(keep=False) # 在 df1中不在df2中
df_diff2 = pd.concat([df2,df1,df1]).drop_duplicates(keep=False) # 在 df2中不在df1中 -
shape dfshape[0] 相对 shape[1] 更常用(统计打印数据行数)
hg.shape返回的是hg的行数和列数
hg.shape[0]返回的是hg的行数,有几行 同 len(df.index)
hg.shape[1]返回的是hg的列数,有几列 同 len(data.columns)
data.columns.tolist() 是list,data.columns 是object,不影响什么 都可以使用 for 或者in -
聚合重命名 (会一种即可,其他可看懂也好)
使用rename 函数,比较好理解
gender_df2 = df.groupby(“Gender”, as_index=False).agg({‘CustomerID’:‘count’}).rename(columns={‘CustomerID’: ‘user_count’})as_index须为True,即作为索引返回,agg(’new列名‘=(’列名‘, ’统计方法‘)),注意是括号()
gender_df3 = df.groupby(“Gender”).agg(user_count=(‘CustomerID’,‘count’))groupby(as_index=False)[‘列名’]的方式,注意这种方式as_index须为False
gender_df4 = df.groupby(‘Gender’, as_index=False)[‘CustomerID’].agg({“user_count”: “count”})稍微多说:
重要技巧: groupby之后直接.reset_index()可以得到一个没有多级索引的DataFrame
df1= df.groupby([‘date’])[‘price’].agg({‘sum’,‘count’}).reset_index()def sum_sales_day(df, gpcols, sumcol, newname): salesSumByDay = df.groupby(gpcols)[sumcol].sum().reset_index(name=newname) # 实现分组聚合汇总,重命名字段 sumcol-->newname # salesSumByDay.loc[:, 'view_dimension'] = 1 return salesSumByDay myglobal2 = data1.groupby(sumList, as_index=False).agg({'qty': ['sum'], 'bo_process_qty': ['sum'], 'pre_process_qty': ['sum'], 'bo_qty': ['sum'], })
-
关于一些数据类型 dataframe
使用 values函数
r = data[['id']].values print(r,type(r)) # <class 'numpy.ndarray'> print(r[0],type(r[0])) # <class 'numpy.ndarray'> print(r[0][0],type(r[0][0])) # <class 'str'> 或者 <class 'numpy.int64'> 等 根据各字段dtypes print('-------') 例如: [[7058] [7059] [7072] [7054]] <class 'numpy.ndarray'> [7058] <class 'numpy.ndarray'> 7058 <class 'numpy.int64'> r = data['name'].values print(r,type(r)) # <class 'numpy.ndarray'> print(r[0],type(r[0])) # <class 'str'> 或者 <class 'numpy.int64'> 等 根据各字段dtypes print(r[0][0],type(r[0][0])) # 数字的话报错 IndexError: invalid index to scalar variable. 越界 好理解 字符串的话首个字母 print('-------') 例如: ['sravan' 'jyothika' 'harsha' 'ramya'] <class 'numpy.ndarray'> sravan <class 'str'> s <class 'str'>
使用聚合函数
x = data[[‘id’]].mean()
y = data[‘id’].max()
r = x+y说明: Series 和 numpy.int64 是可以一起运算的竟然(当然 data[['id']].mean() + data[['id']].max() 肯定是可以的 Series+Series) data['id'].mean() + data['id'].max() 肯定也是可以的 x = data['id'].mean() y = data['id'].max() r = x+y print(x,type(x)) print(y,type(y)) print(r,type(r)) 打印: 7060.75 <class 'numpy.float64'> 7072 <class 'numpy.int64'> 14132.75 <class 'numpy.float64'> 14132.75 <class 'float'>
print(x,type(x))
print(y,type(y))
print(r,type®)r = r.tolist()
print®
打印:
id 7060.75
dtype: float64 <class ‘pandas.core.series.Series’>
7072 <class ‘numpy.int64’>
id 14132.75
dtype: float64 <class ‘pandas.core.series.Series’>
[14132.75]beginDate = hisData[‘order_date’].min() str类型数据 可以直接使用的(int 或者float的也是可以直接使用的(当成pyton基本数据类型))
dist_ch_wh_lis = fdc_mergeOrderBOD[‘child_warehouse_no’].drop_duplicates().values.tolist()
25.一些日期处理
# 下周周一日期
dtStr = ClassConfig.getConstants()['dt'] # 测试离线数据 使用的预测日期
dtDatetime = datetime.datetime.strptime(dtStr, "%Y-%m-%d")
nextMondayDateStr = datetime.datetime.strftime(dtDatetime + timedelta(7 - dtDatetime.weekday()), "%Y-%m-%d")
# 下个月1号日期
dt = datetime.date(*map(int, dtStr.split('-'))) # str 转为 datetime.date
first_day = datetime.date(dt.year, dt.month, 1) # 该月第一天
days_num = calendar.monthrange(first_day.year, first_day.month)[1] # 获取一个月有多少天
next_month_first_day = first_day + datetime.timedelta(days=days_num) # 当月的最后一天只需要days_num-1即可
nextMonthFirstDateStr = str(next_month_first_day)
26.新dataframe 的赋值 一列多行,一行多列
一列多行 确定给那一列('order_date') 赋 什么值(datesList)
datesList = [xxx,xxx,xxx,...,xxx]
new_df = pd.DataFrame(columns=old_df.columns.tolist())
new_df.loc[:, 'order_date'] = datesList # 一维list赋值(指定列赋值,实现同列的多行赋值)
# 一行多列 确定给哪些列(otherColsList) 赋 什么值(data)
otherColsList = ['seller_no', 'dept_no', 'bod_no', 'wh', 'sku']
data = hisData[otherColsList].drop_duplicates().values #
newhisData.loc[:, otherColsList] = data # 二维list赋值,给同行不同列一起赋值(赋的相同的值) 一行多列赋值
脚本:
dates = ClassUtils.get_date_list(begin_date_str, end_date_str)
# 一维list赋值(指定列赋值,实现同列的多行赋值)
data.loc[:, 'order_date'] = dates
# 二维list赋值,给同行不同列一起赋值
# otherColsList = ['sku', 'qty', 'view_dimension']
otherColsList = df.columns.tolist()
otherColsList.remove('order_date')
otherData = df[otherColsList].drop_duplicates().values # 二维list
data.loc[:, otherColsList] = otherData
脚本:
# 长表改宽表
def fun(df, cols):
newDF = pd.DataFrame(columns=cols)
start_date = df[['date']].min().tolist()
newDF.loc[:, 'start_date'] = start_date # 一列多行 赋值 start_date是list
otherCols = ['name', 'code']
data = df[otherCols].drop_duplicates().values #
newDF.loc[:, otherCols] = data # 一行多列 赋值 data是二维list
# 增加宽表字段
start_date = start_date[0]
for i in range(5):
newCol = 'v' + str(i)
if newCol in cols:
v = df.loc[df['date'] == start_date]['value'].tolist()[0]
newDF.loc[:, newCol] = v
# 日期加1
dt = datetime.datetime.strptime(start_date, "%Y-%m-%d")
dt += timedelta(days=1)
start_date = dt.strftime("%Y-%m-%d")
return newDF
primaryCols = ['name', 'code']
hive2doris = hiveData.groupby(primaryCols, as_index=False).apply(fun, cols=colList).reset_index()
-
pandas iloc 和 loc 温故知新
iloc 使用 语法df.iloc [row selection, column selection]
df.iloc[0] 第一行
df.iloc[-1] 最后一行df.iloc[:,0] 第一列 df.iloc[:,-1] 最后一列 df.iloc[0:3] 第1到3行 df.iloc[:,0:3] 第1到3列 df.iloc[0:3,2:4] 第1到3行且3到4列 df.iloc[[0,3],[2,4]] 第1、4行和3、5列 注意:选择一行时(比如第一行 或者第几行) df.iloc[0] 返回series df.iloc[[0]] 返回dataframe
loc
loc选择数据实例:
df.loc[df[‘fistname’] == ‘Evan’,‘city’:‘web’] # 选择满足条件的 'email’列到’web’的多列数据df.loc[df['email'].str.endwith('.com')] # str 处理比较灵活可以使用 python str的任何函数进行处理 my_df.loc[my_df['往来单位名称'].str.contains('en'), '客户类型'] = '国外客户' df.loc[df['fistname'].isin(['Evan','Arry'])] # df.loc[(df['fistname'].isin(['Evan','Arry']) & df['email'].str.endwith('.com')),'city':'web'] # df.loc[df['company'].apply(lamnda x: len(x.split(' ')) == 4)] 或者 f = df['company'].apply(lamnda x: len(x.split(' ')) == 4) df.loc[f,['email','web']] # 选择满足条件的 'email','web'两列数据 当然 使用loc后进行赋值可是可以的 df.loc[df['fistname'].isin(['Evan','Arry']),['fistname']] == '宏哥' res.loc[:, 'order_date'] = res['order_date'].map(str).str[0:10] 补数据处理:添加一行数据,指定某字段为XXX值 seriesData = df.iloc[len(df.index) - 1, :] # 选择最后一行数据(是一个 series) seriesData['order_date'] = 'XXX' # 将 seriesData 的字段 order_date 赋值为 XXX seriesData['qty'] = 0 # 将 seriesData 的字段 qty 赋值为 0 df = df.append(seriesData, ignore_index=True) # 拼接到原来df 回想前面 append使用 增加一行数据,指定了字段的 正常赋值 未指定字段值的默认为NAN df = df.append({"company": '', "contacts": ''}, ignore_index=True) # 插入一行company、contacts都是''的数据(其他字段默认为NAN) bodNode = bodNode.append(bodNode.iloc[0],ignore_index=True) # iloc[0] 入参是一个数字时,返回的是一个sries 可以直接append 获取索引,通过索引操作数据 eg: dropindex = df[(df['sku_qtty'] - df['threshold']) > 0] re_pprsed = df.drop(dropindex.index) # 一般drop处理是否使用参数inplace(当场生效) 自己看,删除列好理解 df = df.drop(columns=[列名list])
-
数据 行维度的排序处理 分组处理 和 重命名
排序,为两个df进行datacompy预处理(确定每行数据一致)
ColsSortList = [‘order_date’, ‘wh_no’, ‘sku_id’, ‘order_no’, ‘sku_qtty’]
ret1 = resultColSet.sort_values(by=ColsSortList, ascending=[False, False, False, False, False]).reset_index(drop=True)分组处理 并且排序
colSubList = [‘seller_no’, ‘dept_no’, ‘bod_no’, ‘wh_no’, ‘sku_id’]
df_cnt = df.groupby(colSubList).size().reset_index(name=‘cnt’) # 分组统计 ,对处理后的字段重命名(reset_index(name=‘新列名’))
data = df_cnt.sort_values(by=[‘cnt’], ascending=False) # 根据 cnt 排序前面有讲过,groupby函数是否使用as_index=False, 是否指定为索引。
-
数据补充处理
参考26
# 确定数据补齐范围
df_date_list = df_sorted[‘order_date’].values.tolist() # 日销量有数据的 日期
full_date_list = get_date_list(df_date_list[0], enddate) # 日销量 全量日期
ret_date_list = [i for i in full_date_list if i not in df_date_list] # 待补0处理的 日期# 根据原df数据字段,创建new_df进行数据补齐(qty 补齐为0) columns = columns=old_df.columns.tolist() new_df = pd.DataFrame(columns=columns) new_df.loc[:, 'order_date'] = ret_date_list # 一维list赋值(指定列赋值,实现同列的多行赋值) new_df.loc[:, 'qty'] = 0 # 字段补充为0 # new_df 主键字段数据补齐 columns.remove('order_date') columns.remove('qty') data = df[columns].drop_duplicates().values # new_df.loc[:, columns] = data # 二维list赋值,给同行不同列一起赋值(赋的相同的值) 一行多列赋值 原来df 和 补齐后 的new_df 进行行拼接 结果为最终补齐数据 返回即可 (这里索引可重置一下) df_result = pd.concat([df, new_df], axis=0, ignore_index=True) # 拼接入参的df 和 补0处理后的df 以上是补齐数据补齐值是固定的(比如补齐为0等),如果补齐的数据是有逻辑的或者不同行补齐的数据不一致: 可以 利用python 创建出需要补齐数据的二维list,再对df进行使用append函数 origin_dates = df_sorted['order_date'].values.tolist() # 日销量有数据的 日期 full_dates = ClassUtils.get_date_list(df_date_list[0], endDateStr) # 日销量 全量日期 fill_dates = [i for i in full_dates if i not in origin_dates] # 待补0处理的 日期 append_dates = [] for i in fill_dates: tmp = [] tmp[0] = i # 更新模板数据的 日期值 tmp[1] = 0 # 更新模板数据的 日销量值 tmp[2] = 0 # 更新模板数据的 日销量值 tmp[3] = 0 # 更新模板数据的 日销量值 . . . append_dates.append(tmp) # 生成二维list ,添加到数组 写的更加高度一点,tmp的list可以使用模板方式,指定index model_data_list = df_sorted.head(1).values.tolist()[0] # 模板数据 第一行数据 date_index = model_data_list.index(df_date_list[0]) # 获取模板数据中 对应日期的索引值 ... 或者 ... id_index = data.columns.get_loc('id') # 获取指定字段索引(列)的index 改部分以及tmp[] 都可放到for之外(减少资源损耗) name_index = data.columns.get_loc('name') subjects_index = data.columns.get_loc('subjects') tmp = [] tmp[id_index] = i # tmp[name_index] = XXX # tmp[subjects_index] = XXXX #
-
按日期汇总的demo
按照月汇总
df[‘order_date’] = pd.to_datetime(df[‘order_date’]) # 将数据类型转换为日期类型
df = df.set_index(‘order_date’) # 将date设置为index
df = df.resample(‘MS’).sum()
df = df.reset_index()
df.loc[:, ‘view_dimension’] = 3
df[‘order_date’] = df[‘order_date’].astype(‘str’).str[0:10] # 日期转为str -
读取csv数据 demo
fileName3 = 'jtv1ByDay.csv' if os.path.exists(filePathTmp + fileName3): jtv1ByDay = pd.read_csv(filePathTmp + fileName3, dtype={'version': object, 'sku': object, 'qty': float, 'view_dimension': object}) else: if self.jtv1.empty: jtv1ByDay = self.jtv1 else: splitColList = ['version', 'seller_no', 'dept_no', 'sku', 'business_mode_id', 'brand_id', 'order_date'] splitData = self.jtv1.groupby(splitColList, as_index=False).apply( ClassUtils.week2dayfun).reset_index() jtv1ByDay = splitData[ ['version', 'order_date', 'seller_no', 'dept_no', 'sku', 'qty', 'business_mode_id', 'brand_id', 'view_dimension']] ClassUtils.toCsv(jtv1ByDay, filePathTmp, fileName3)