frequency as "index" in Pandas dataframe and dynamic extension
Question:
I am working on an RF-project and the workflow is as follows:
- Define
start
, stop
, step
of the desired frequency sweep
- Calculate the specific line impedance
Z0
and frequency dependent effective permittivity eef
for every frequency
- Calculate the Transmission matrix
ABCD
for every f
- From ABCD calculate the scattering Parameters S (complex valued)
- Calculate the magnitude of the params
- Finally plot them
At the moment I have it implemented with lists/numpy-arrays being filled in a for-loop. This works but is ugly and labor intensive when I want to daisychain multiple ABCD’s.
The issues are a difficult debugging (loosing temporary results between the aforementioned steps.
I think a solution might be using pandas but I have difficulties to implement the following necessities:
- Create dataframe with the frequency as the index (a column for f might work too)
- expand the columns "on the fly" to store the results for every f
- individually set
dtype=
for some colums because currently dtype=np.clongdouble
is necessary (otherwise I get div by zero in latter calculations)
- one column containing a numpy-array (the abcd-matrix)
I’ve searched around but the results didn’t clarify the needed concepts and my approach like
>>> import pandas as pd
>>> start = int(100E6)
>>> stop = int(1E9)
>>> step = int(1E6)
>>> df = pd.DataFrame(index=range(start,stop+step,step),columns=["z0","eef"])
>>> df.index
RangeIndex(start=100000000, stop=1001000000, step=1000000)
>>>
throws errors when I try to access df.index(1000)
or df[1000]
Answers:
In order to make it more future proof, one can create a function that helps one calculate the S-params given f_start
, f_stop
, f_step
, and the functions Z0
, and eef
. Here I will consider one has already the functions Z0
and eef
.
Let’s call the function calc_s
(the comments make it self-explanatory)
import pandas as pd
def calc_s(f_start, f_stop, f_step, Z0, eef):
# Create a dataframe with the frequency column
df = pd.DataFrame({'f': pd.Series(range(f_start, f_stop, f_step))})
# Calculate Z0 and eef and add them to the dataframe
df['Z0'] = df['f'].apply(Z0)
df['eef'] = df['f'].apply(eef)
# Calculate the ABCD matrix for every frequency
df['A'] = 1
df['B'] = 2 * df['Z0'] * df['eef']**0.5
df['C'] = 1
df['D'] = -2 * df['Z0'] * df['eef']**0.5
# Calculate the scattering parameters S
df['S11'] = (df['A'] + df['B']) / (df['A'] - df['B'])
df['S12'] = 2 * df['C'] / (df['A'] - df['B'])
df['S21'] = 2 * df['C'] / (df['A'] - df['B'])
df['S22'] = (df['A'] - df['B']) / (df['A'] + df['B'])
# Calculate the magnitude of the scattering parameters
df['S11_mag'] = df['S11'].apply(lambda x: x.real**2 + x.imag**2)**0.5
df['S12_mag'] = df['S12'].apply(lambda x: x.real**2 + x.imag**2)**0.5
df['S21_mag'] = df['S21'].apply(lambda x: x.real**2 + x.imag**2)**0.5
df['S22_mag'] = df['S22'].apply(lambda x: x.real**2 + x.imag**2)**0.5
return df
Note that if needed, one cal always adjust the calculations, or change it to other that one might want/need, be it removing, adding,…
Now, let’s test the function above with some dummy data
f_start = 1
f_stop = 100
f_step = 1
Z0 = lambda f: 50 # This is a dummy function that returns 50 for every f
eef = lambda f: 1 # This is a dummy function that returns 1 for every f
df = calc_s(f_start, f_stop, f_step, Z0, eef)
[Out]:
f Z0 eef A B ... S22 S11_mag S12_mag S21_mag S22_mag
0 1 50 1 1 100.0 ... -0.980198 1.020202 0.020202 0.020202 0.980198
1 2 50 1 1 100.0 ... -0.980198 1.020202 0.020202 0.020202 0.980198
2 3 50 1 1 100.0 ... -0.980198 1.020202 0.020202 0.020202 0.980198
3 4 50 1 1 100.0 ... -0.980198 1.020202 0.020202 0.020202 0.980198
4 5 50 1 1 100.0 ... -0.980198 1.020202 0.020202 0.020202 0.980198
[5 rows x 15 columns]
After messing around for a while I am fine with the following
df = pd.DataFrame(columns=['Z0', 'eef', 'A', 'B', 'C',
'D', 's11', 's12', 's21', 's22'], index=f, dtype=bool)
df.index.name = 'Frequency'
with f = [*range(start, stop+step, step)]
. Then I am accessing the necessary column/row by df["A"].loc(f)
. Even if the column is not used in further calculations I stick to this scheme because I had too much hassle to distinguish between Dataframes with(-out) specific columns. During the process the dtype=
is expanded to complex
but that’s working like a charm now.
I am working on an RF-project and the workflow is as follows:
- Define
start
,stop
,step
of the desired frequency sweep - Calculate the specific line impedance
Z0
and frequency dependent effective permittivityeef
for every frequency - Calculate the Transmission matrix
ABCD
for every f - From ABCD calculate the scattering Parameters S (complex valued)
- Calculate the magnitude of the params
- Finally plot them
At the moment I have it implemented with lists/numpy-arrays being filled in a for-loop. This works but is ugly and labor intensive when I want to daisychain multiple ABCD’s.
The issues are a difficult debugging (loosing temporary results between the aforementioned steps.
I think a solution might be using pandas but I have difficulties to implement the following necessities:
- Create dataframe with the frequency as the index (a column for f might work too)
- expand the columns "on the fly" to store the results for every f
- individually set
dtype=
for some colums because currentlydtype=np.clongdouble
is necessary (otherwise I get div by zero in latter calculations) - one column containing a numpy-array (the abcd-matrix)
I’ve searched around but the results didn’t clarify the needed concepts and my approach like
>>> import pandas as pd
>>> start = int(100E6)
>>> stop = int(1E9)
>>> step = int(1E6)
>>> df = pd.DataFrame(index=range(start,stop+step,step),columns=["z0","eef"])
>>> df.index
RangeIndex(start=100000000, stop=1001000000, step=1000000)
>>>
throws errors when I try to access df.index(1000)
or df[1000]
In order to make it more future proof, one can create a function that helps one calculate the S-params given f_start
, f_stop
, f_step
, and the functions Z0
, and eef
. Here I will consider one has already the functions Z0
and eef
.
Let’s call the function calc_s
(the comments make it self-explanatory)
import pandas as pd
def calc_s(f_start, f_stop, f_step, Z0, eef):
# Create a dataframe with the frequency column
df = pd.DataFrame({'f': pd.Series(range(f_start, f_stop, f_step))})
# Calculate Z0 and eef and add them to the dataframe
df['Z0'] = df['f'].apply(Z0)
df['eef'] = df['f'].apply(eef)
# Calculate the ABCD matrix for every frequency
df['A'] = 1
df['B'] = 2 * df['Z0'] * df['eef']**0.5
df['C'] = 1
df['D'] = -2 * df['Z0'] * df['eef']**0.5
# Calculate the scattering parameters S
df['S11'] = (df['A'] + df['B']) / (df['A'] - df['B'])
df['S12'] = 2 * df['C'] / (df['A'] - df['B'])
df['S21'] = 2 * df['C'] / (df['A'] - df['B'])
df['S22'] = (df['A'] - df['B']) / (df['A'] + df['B'])
# Calculate the magnitude of the scattering parameters
df['S11_mag'] = df['S11'].apply(lambda x: x.real**2 + x.imag**2)**0.5
df['S12_mag'] = df['S12'].apply(lambda x: x.real**2 + x.imag**2)**0.5
df['S21_mag'] = df['S21'].apply(lambda x: x.real**2 + x.imag**2)**0.5
df['S22_mag'] = df['S22'].apply(lambda x: x.real**2 + x.imag**2)**0.5
return df
Note that if needed, one cal always adjust the calculations, or change it to other that one might want/need, be it removing, adding,…
Now, let’s test the function above with some dummy data
f_start = 1
f_stop = 100
f_step = 1
Z0 = lambda f: 50 # This is a dummy function that returns 50 for every f
eef = lambda f: 1 # This is a dummy function that returns 1 for every f
df = calc_s(f_start, f_stop, f_step, Z0, eef)
[Out]:
f Z0 eef A B ... S22 S11_mag S12_mag S21_mag S22_mag
0 1 50 1 1 100.0 ... -0.980198 1.020202 0.020202 0.020202 0.980198
1 2 50 1 1 100.0 ... -0.980198 1.020202 0.020202 0.020202 0.980198
2 3 50 1 1 100.0 ... -0.980198 1.020202 0.020202 0.020202 0.980198
3 4 50 1 1 100.0 ... -0.980198 1.020202 0.020202 0.020202 0.980198
4 5 50 1 1 100.0 ... -0.980198 1.020202 0.020202 0.020202 0.980198
[5 rows x 15 columns]
After messing around for a while I am fine with the following
df = pd.DataFrame(columns=['Z0', 'eef', 'A', 'B', 'C',
'D', 's11', 's12', 's21', 's22'], index=f, dtype=bool)
df.index.name = 'Frequency'
with f = [*range(start, stop+step, step)]
. Then I am accessing the necessary column/row by df["A"].loc(f)
. Even if the column is not used in further calculations I stick to this scheme because I had too much hassle to distinguish between Dataframes with(-out) specific columns. During the process the dtype=
is expanded to complex
but that’s working like a charm now.