{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Writing pandas dataframes as fortran-formatted csv files\n",
"\n",
"Say you have a pandas dataframe, `df`, and you need to write that dataframe as a fortran-readable csv file with a very specific formatting. Here's a simple approach that makes use of the [fortranformat](https://pypi.org/project/fortranformat/) package. \n",
"\n",
"The following notebook builds up a working example, but if you just want the minimal code snippet to understand, here it is: \n",
"\n",
"```\n",
"import fortranformat as ff\n",
"\n",
"format_string='(a20, f15.2, f15.2, f15.5 ,i15)'\n",
"header_line = ff.FortranRecordWriter(format_string)\n",
"Formatted_df=df.apply(lambda x : header_line.write(x.values),axis=1)\n",
"```\n",
"\n",
"The final object, `Formatted_df` will be a `Series` the length of the original dataframe and a given element in the `Series` will be the formatted string for the corresponding row in the dataframe. Once you have that, you can write it to a file with \n",
"\n",
"```\n",
"Formatted_df.to_csv('formatted_df.csv',index=False,header=False)\n",
"```\n",
"\n",
"The rest of this notebook builds a test dataframe, applies the above formatting and outputs it. Throughout, I'm assuming you're already familiar with fortran read/write formatting. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## a working example\n",
"\n",
"Let's start by importing all the things we'll use and creating a function to build a dataframe with a mix of data types: "
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"\n",
"import fortranformat as ff, pandas as pd, numpy as np\n",
"import os, random,string,time\n",
"import matplotlib.pyplot as plt \n",
"\n",
"def buildTestData(Nrows):\n",
" ''' build a test dataframe with a mix of strings, integers and floats with number of rows as input''' \n",
" samp_names = [] \n",
" for sampNum in range(0,Nrows):\n",
" Nchars=int(np.random.rand(1)[0]*10)+3 \n",
" samp_names.append(''.join(random.choices(string.ascii_uppercase + string.digits, k = Nchars)))\n",
" \n",
" df = pd.DataFrame({\n",
" 'sample_name': samp_names, \n",
" 'lat' : np.linspace(30,14,Nrows),\n",
" 'lon' : np.linspace(200,230,Nrows),\n",
" 'obs' : np.random.random(Nrows),\n",
" 'Nobs': (np.random.random(Nrows)*100).astype(int)\n",
" })\n",
" return df "
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
sample_name
\n",
"
lat
\n",
"
lon
\n",
"
obs
\n",
"
Nobs
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
6USTN45ZNOR
\n",
"
30.000000
\n",
"
200.000000
\n",
"
0.518470
\n",
"
0
\n",
"
\n",
"
\n",
"
1
\n",
"
V2A9E
\n",
"
29.838384
\n",
"
200.303030
\n",
"
0.940106
\n",
"
39
\n",
"
\n",
"
\n",
"
2
\n",
"
A5O1Q940
\n",
"
29.676768
\n",
"
200.606061
\n",
"
0.993028
\n",
"
65
\n",
"
\n",
"
\n",
"
3
\n",
"
IG98UF0
\n",
"
29.515152
\n",
"
200.909091
\n",
"
0.665703
\n",
"
58
\n",
"
\n",
"
\n",
"
4
\n",
"
0WXBCQU67O
\n",
"
29.353535
\n",
"
201.212121
\n",
"
0.844105
\n",
"
27
\n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" sample_name lat lon obs Nobs\n",
"0 6USTN45ZNOR 30.000000 200.000000 0.518470 0\n",
"1 V2A9E 29.838384 200.303030 0.940106 39\n",
"2 A5O1Q940 29.676768 200.606061 0.993028 65\n",
"3 IG98UF0 29.515152 200.909091 0.665703 58\n",
"4 0WXBCQU67O 29.353535 201.212121 0.844105 27"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df = buildTestData(100)\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, we initialize the `fortranformat` record writer with a fortan format string: "
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"format_string='(a20, f15.2, f15.2, f15.5 ,i15)'\n",
"header_line = ff.FortranRecordWriter(format_string)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To apply the record writer to a single row of the dataframe, you can do: "
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"' 6USTN45ZNOR 30.00 200.00 0.51847 0'"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"header_line.write(df.loc[0].values)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"but we wnat to use it on all the rows. As I show at the end of this notebook, using `df.apply` is faster than manually looping: "
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"Formatted_df=df.apply(lambda x : header_line.write(x.values),axis=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"What comes out is a `pandas.Series` object, where each record is a single row of the original dataframe formatted as a string: "
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0 6USTN45ZNOR 30.00 20...\n",
"1 V2A9E 29.84 20...\n",
"2 A5O1Q940 29.68 20...\n",
"3 IG98UF0 29.52 20...\n",
"4 0WXBCQU67O 29.35 20...\n",
"dtype: object"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Formatted_df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So to write it out, you can just use `Formatted_df.to_csv`. The following initializes a file with a few header lines containing the colum names and the format string that we used to generate the file: "
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"output_fi='formatted_df.csv'\n",
"col_names=df.columns.tolist()\n",
"with open(output_fi,'w') as outfi: \n",
" outfi.write('# '+' '.join(col_names)+\"\\n\")\n",
" outfi.write('# '+format_string+\"\\n\")\n",
" \n",
"Formatted_df.to_csv(output_fi,mode='a',index=False,header=False)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So let's take a look at the first 10 lines of the file we just wrote: "
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"# sample_name lat lon obs Nobs\n",
"\n",
"# (a20, f15.2, f15.2, f15.5 ,i15)\n",
"\n",
" 6USTN45ZNOR 30.00 200.00 0.51847 0\n",
"\n",
" V2A9E 29.84 200.30 0.94011 39\n",
"\n",
" A5O1Q940 29.68 200.61 0.99303 65\n",
"\n",
" IG98UF0 29.52 200.91 0.66570 58\n",
"\n",
" 0WXBCQU67O 29.35 201.21 0.84411 27\n",
"\n",
" WVT50 29.19 201.52 0.98179 34\n",
"\n",
" UQRASDW 29.03 201.82 0.41387 95\n",
"\n",
" ZN2T 28.87 202.12 0.83129 77\n",
"\n"
]
}
],
"source": [
"outfi = open(output_fi,'r')\n",
"for i in range(10): \n",
" print(outfi.readline())\n",
"outfi.close() "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**To read the file back in**, we can use `pd.read_csv` with `delim_whitespace=True` to signify variable whitespace as the delimiter. "
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
sample_name
\n",
"
lat
\n",
"
lon
\n",
"
obs
\n",
"
Nobs
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
6USTN45ZNOR
\n",
"
30.00
\n",
"
200.00
\n",
"
0.51847
\n",
"
0
\n",
"
\n",
"
\n",
"
1
\n",
"
V2A9E
\n",
"
29.84
\n",
"
200.30
\n",
"
0.94011
\n",
"
39
\n",
"
\n",
"
\n",
"
2
\n",
"
A5O1Q940
\n",
"
29.68
\n",
"
200.61
\n",
"
0.99303
\n",
"
65
\n",
"
\n",
"
\n",
"
3
\n",
"
IG98UF0
\n",
"
29.52
\n",
"
200.91
\n",
"
0.66570
\n",
"
58
\n",
"
\n",
"
\n",
"
4
\n",
"
0WXBCQU67O
\n",
"
29.35
\n",
"
201.21
\n",
"
0.84411
\n",
"
27
\n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" sample_name lat lon obs Nobs\n",
"0 6USTN45ZNOR 30.00 200.00 0.51847 0\n",
"1 V2A9E 29.84 200.30 0.94011 39\n",
"2 A5O1Q940 29.68 200.61 0.99303 65\n",
"3 IG98UF0 29.52 200.91 0.66570 58\n",
"4 0WXBCQU67O 29.35 201.21 0.84411 27"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df = pd.read_csv(output_fi,skiprows=2,names=col_names,delim_whitespace=True)\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If we didn't already know the column names, we could have parsed the header rows to figure it out.\n",
"\n",
"**So in summary, the following code does it all:**"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"format_string='(a20, f15.2, f15.2, f15.5 ,i15)'\n",
"header_line = ff.FortranRecordWriter(format_string)\n",
"\n",
"Formatted_df=df.apply(lambda x : header_line.write(x.values),axis=1)\n",
"\n",
"output_fi='formatted_df.csv'\n",
"col_names=df.columns.tolist()\n",
"with open(output_fi,'w') as outfi: \n",
" outfi.write('# '+' '.join(col_names)+\"\\n\")\n",
" outfi.write('# '+format_string+\"\\n\")\n",
" \n",
"Formatted_df.to_csv(output_fi,mode='a',index=False,header=False)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## controlling column ordering \n",
"One important caveat is that we've assumed that we know the dataframe column ordering. If we didn't, or if we wanted to change the order for writing, we could do the following:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(f15.2 , a20 , f15.2 , f15.5 , i15)\n"
]
},
{
"data": {
"text/plain": [
"0 30.00 6USTN45ZNOR 20...\n",
"1 29.84 V2A9E 20...\n",
"2 29.68 A5O1Q940 20...\n",
"3 29.52 IG98UF0 20...\n",
"4 29.35 0WXBCQU67O 20...\n",
" ... \n",
"95 14.65 078A54H2 22...\n",
"96 14.48 22Z4DFF4V 22...\n",
"97 14.32 EQ7DYKZB 22...\n",
"98 14.16 66B0B24AN 22...\n",
"99 14.00 EUZ 23...\n",
"Length: 100, dtype: object"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# specifiy format of each as an ordered dict, moving the sample_name to second column\n",
"import collections \n",
"format_dict=collections.OrderedDict(lat='f15.2',sample_name='a20',lon='f15.2',obs='f15.5',Nobs='i15')\n",
"\n",
"# build the format string \n",
"format_string='('+\" , \".join(format_dict.values()) + ')'\n",
"print(format_string)\n",
"header_line = ff.FortranRecordWriter(format_string)\n",
"\n",
"# now use apply, but pull the columns using the ordered dict keys to ensure ordering: \n",
"Formatted_df=df.apply(lambda x : header_line.write(x[format_dict.keys()]),axis=1)\n",
"Formatted_df\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Is it faster than looping? (yes)\n",
"\n",
"While using `apply` with a fortranformat `FortranRecordWriter` is nice and tidy, it's not clear that it will be any faster than a manual iteration over the records, as `apply` will end up looping under the hood. So here's a test! The following function captures everything above with an additional argument, `method`. If `method` is `apply`, then we'll build a dataframe of `Nrows` and format it exactly as above. If the method is `iter`, then we'll manually loop over the dataframe and format each row ourselves (using the same fortranformat writer). "
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"def testCase(Nrows,method='apply'):\n",
" df = buildTestData(Nrows)\n",
" format_string='(a10, f15.2, f15.2, f15.5 ,i15)'\n",
" header_line = ff.FortranRecordWriter(format_string)\n",
" \n",
" # set up the output file \n",
" output_fi='formatted_df.csv'\n",
" col_names=df.columns.tolist()\n",
" with open(output_fi,'w') as outfi: \n",
" outfi.write('# '+' '.join(col_names)+\"\\n\")\n",
" outfi.write('# '+format_string+\"\\n\")\n",
" \n",
" if method=='apply': \n",
" Formatted_df=df.apply(lambda x : header_line.write(x.values),axis=1) \n",
" Formatted_df.to_csv(output_fi,mode='a',index=False,header=False)\n",
" elif method=='iter':\n",
" outfi=open(output_fi,'a')\n",
" for rowid,vals in df.iterrows(): \n",
" outfi.write(header_line.write(vals.values)+\"\\n\" )\n",
" outfi.close()\n",
" return None"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The time savings will probably be most noticeable for large dataframes, so we'll run both methods for a range of dataframe sizes: "
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"runTimes={'apply':[],'iter':[],'nrows':[]}\n",
"\n",
"for nRows in np.logspace(2,6,10).astype('int'):\n",
" runTimes['nrows'].append(nRows)\n",
" for meth in ['iter','apply']:\n",
" time1= time.time()\n",
" run=testCase(nRows,method=meth)\n",
" runTimes[meth].append(time.time()-time1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And now let's plot the computation time of each: "
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"