{ "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# Working with pandas in pandapower\n", "\n", "All data in pandapower is stored in pandas dataframes, which is why all functionalities that are implemented in pandas (http://pandas.pydata.org/) can be used with pandapower. pandas is a very powerful and widely used tool for data analysis.\n", "\n", "In the following we will present just a few of the many possibilities how builtin pandas functions can make your life easier when working in pandapower.\n", "\n", "This tutorial refers to pandas functionalities and how to use them with pandapower. If you have not worked with pandas before, this should give you a good overview of the relevant functionality. If you are already familiar with pandas, it might still refresh your memory, but maybe not provide new information." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We use the mv_oberrhein example from the pandapower networks package for this tutorial." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "#Import the pandapower and the networks module:\n", "import pandapower as pp\n", "import pandapower.networks as nw\n", "import pandas as pd\n", "\n", "#Import an example network:\n", "net = nw.mv_oberrhein()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Accessing and Setting Parameters\n", "\n", "You can accesss all parameters of one element with the .loc command:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "name Line 4\n", "std_type NA2XS2Y 1x185 RM/25 12/20 kV\n", "from_bus 239\n", "to_bus 236\n", "length_km 0.381968\n", "r_ohm_per_km 0.161\n", "x_ohm_per_km 0.117\n", "c_nf_per_km 273.0\n", "g_us_per_km 0.0\n", "max_i_ka 0.362\n", "df 1.0\n", "parallel 1\n", "type cs\n", "in_service True\n", "Name: 4, dtype: object" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#Detailed information about line 4\n", "net.line.loc[4]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or one parameter of multiple elements:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5 3.3028\n", "6 0.3504\n", "Name: length_km, dtype: float64" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#Length information about line 5 and 6\n", "net.line.length_km.loc[[5,6]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The values are returned as a pandas series. To get them in an array instead, use the values function:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([3.3028, 0.3504])" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#Length information about line 5 and 6 as numpy array\n", "net.line.length_km.loc[[5,6]].values" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Multiple parameters of multiple elements are returned as a pandas dataframe:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
length_kmr_ohm_per_kmx_ohm_per_kmc_nf_per_km
53.30280.1610.117273.0
60.35040.1610.117273.0
\n", "
" ], "text/plain": [ " length_km r_ohm_per_km x_ohm_per_km c_nf_per_km\n", "5 3.3028 0.161 0.117 273.0\n", "6 0.3504 0.161 0.117 273.0" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#Length and type information about line 5 and 6 \n", "net.line.loc[[5,6], [\"length_km\", \"r_ohm_per_km\", \"x_ohm_per_km\", \"c_nf_per_km\"]]" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "The values function in this case yields a multi-dimensional array:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[3.3028e+00, 1.6100e-01, 1.1700e-01, 2.7300e+02],\n", " [3.5040e-01, 1.6100e-01, 1.1700e-01, 2.7300e+02]])" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#Length and type information about line 5 and 6 as an array \n", "net.line.loc[[5,6], [\"length_km\", \"r_ohm_per_km\", \"x_ohm_per_km\", \"c_nf_per_km\"]].values" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To access exactly one parameter, .at can be used instead of .loc:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.3504" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.line.length_km.at[6]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This .at solution can only be used for exactly one parameter, but is siginifcantly faster than using .loc. That is why you should always use .at if possible!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Setting parameters works the same way:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Line lengths after parameter setting:\n" ] }, { "data": { "text/plain": [ "4 1.8\n", "5 3.2\n", "6 2.2\n", "Name: length_km, dtype: float64" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#Set single parameter with .at\n", "net.line.length_km.at[5] = 3.2\n", "#or\n", "net.line.at[5, \"length_km\"] = 3.2\n", "\n", "#Set multiple parameters with .loc\n", "net.line.length_km.loc[[4,6]] = [1.8, 2.2]\n", "\n", "#Output\n", "print(\"Line lengths after parameter setting:\")\n", "net.line.length_km.loc[[4,5,6]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Statistical Evaluations\n", "\n", "You can easily find maximum, minimum values of a column with pandas, e.g." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Maximum Line Length: 3.20 km\n", "Minimum Line Length: 0.07 km\n", "Mean Line Length: 0.62 km\n" ] } ], "source": [ "print(\"Maximum Line Length: %.2f km\"%net.line.length_km.max())\n", "print(\"Minimum Line Length: %.2f km\"%net.line.length_km.min())\n", "print(\"Mean Line Length: %.2f km\"%net.line.length_km.mean())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Of course you can also combine these, for example to calculate weighted means:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Weighted Mean Resistance: 0.16 Ohm per kilometer\n" ] } ], "source": [ "weighted_mean_r = (net.line.length_km * net.line.r_ohm_per_km).sum() / net.line.length_km.sum()\n", "print(\"Weighted Mean Resistance: %.2f Ohm per kilometer\"%weighted_mean_r)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Iterating over Elements\n", "\n", "If you want to iterate over a dataframe, use the iterrows function. It gives you the index of each row and the row as a pandas series." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "the line with index 4 is 1.800000 kilometers long and has a resistance of 0.161000 ohm per kilometers\n", "the line with index 5 is 3.200000 kilometers long and has a resistance of 0.161000 ohm per kilometers\n", "the line with index 6 is 2.200000 kilometers long and has a resistance of 0.161000 ohm per kilometers\n" ] } ], "source": [ "for lidx, linerow in net.line.loc[[4,5,6]].iterrows():\n", " print(\"the line with index %s is %f kilometers long and has a resistance of %f ohm per kilometers\" %(lidx, linerow.length_km, linerow.r_ohm_per_km))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also group elements that share the same value in any column of the dataframe with the groubpy function of pandas;" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "there are 10 lines with standard type 243-AL1/39-ST1A 20.0 with an overall length of 10.761400 kilometers\n", "there are 160 lines with standard type NA2XS2Y 1x185 RM/25 12/20 kV with an overall length of 96.806343 kilometers\n", "there are 11 lines with standard type NA2XS2Y 1x240 RM/25 12/20 kV with an overall length of 4.343042 kilometers\n" ] } ], "source": [ "for std_type, linetable in net.line.groupby(net.line.std_type):\n", " print(\"there are %u lines with standard type %s with an overall length of %f kilometers\"%(len(linetable), std_type, linetable.length_km.sum()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first variable is now the value of the column you grouped by (here line standard type) and the second variable is a pandas dataframe of all lines that have this value (here all lines with the standard type defined in the first variable)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also directly sum up certain values of a groupby, for example" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "std_type\n", "243-AL1/39-ST1A 20.0 10.761400\n", "NA2XS2Y 1x185 RM/25 12/20 kV 96.806343\n", "NA2XS2Y 1x240 RM/25 12/20 kV 4.343042\n", "Name: length_km, dtype: float64" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.line.groupby(net.line.std_type).sum().length_km" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "gives you a pandas of series with the sum of all line length grouped by standard type." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Querying Dataframes\n", "\n", "You will often need to select elements with specific characteristics. You can do that with boolean masks:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "mask = net.line.length_km > 2.5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "the mask variable is now a boolean time series that indicates for every line if it longer than 2500 meters or not. We can now select only the ones which are longer than 2500 meters and output their name and length:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namelength_kmstd_type
5Line 53.200000NA2XS2Y 1x185 RM/25 12/20 kV
45Line 452.611111NA2XS2Y 1x185 RM/25 12/20 kV
59Line 593.155390NA2XS2Y 1x185 RM/25 12/20 kV
91Line 912.528200NA2XS2Y 1x185 RM/25 12/20 kV
162Line 1622.595300243-AL1/39-ST1A 20.0
\n", "
" ], "text/plain": [ " name length_km std_type\n", "5 Line 5 3.200000 NA2XS2Y 1x185 RM/25 12/20 kV\n", "45 Line 45 2.611111 NA2XS2Y 1x185 RM/25 12/20 kV\n", "59 Line 59 3.155390 NA2XS2Y 1x185 RM/25 12/20 kV\n", "91 Line 91 2.528200 NA2XS2Y 1x185 RM/25 12/20 kV\n", "162 Line 162 2.595300 243-AL1/39-ST1A 20.0" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "long_lines = net.line[mask]\n", "\n", "#Output\n", "long_lines[[\"name\", \"length_km\", \"std_type\"]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "or, directly in one step:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namelength_kmstd_type
5Line 53.200000NA2XS2Y 1x185 RM/25 12/20 kV
45Line 452.611111NA2XS2Y 1x185 RM/25 12/20 kV
59Line 593.155390NA2XS2Y 1x185 RM/25 12/20 kV
91Line 912.528200NA2XS2Y 1x185 RM/25 12/20 kV
162Line 1622.595300243-AL1/39-ST1A 20.0
\n", "
" ], "text/plain": [ " name length_km std_type\n", "5 Line 5 3.200000 NA2XS2Y 1x185 RM/25 12/20 kV\n", "45 Line 45 2.611111 NA2XS2Y 1x185 RM/25 12/20 kV\n", "59 Line 59 3.155390 NA2XS2Y 1x185 RM/25 12/20 kV\n", "91 Line 91 2.528200 NA2XS2Y 1x185 RM/25 12/20 kV\n", "162 Line 162 2.595300 243-AL1/39-ST1A 20.0" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.line[net.line.length_km > 2.5][[\"name\", \"length_km\", \"std_type\"]]\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "You can of course also check for exact values, for example if you want all 110 kV buses:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namevn_kvtypezonein_service
58Bus 38110.0nNoneTrue
318Bus 177110.0bNoneTrue
\n", "
" ], "text/plain": [ " name vn_kv type zone in_service\n", "58 Bus 38 110.0 n None True\n", "318 Bus 177 110.0 b None True" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.bus[net.bus.vn_kv == 110]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For string querys, use the str.contains function, for example to find loads with \"MV\" in the name:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namebusp_mwq_mvarconst_z_percentconst_i_percentsn_mvascalingin_servicetype
141MV Load 02150.50.1015290.00.0NaN0.6TrueMV Load
142MV Load 160.50.1015290.00.0NaN0.6TrueMV Load
143MV Load 2480.50.1015290.00.0NaN0.6TrueMV Load
144MV Load 3520.50.1015290.00.0NaN0.6TrueMV Load
145MV Load 4550.50.1015290.00.0NaN0.6TrueMV Load
146MV Load 52350.50.1015290.00.0NaN0.6TrueMV Load
\n", "
" ], "text/plain": [ " name bus p_mw q_mvar const_z_percent const_i_percent sn_mva \\\n", "141 MV Load 0 215 0.5 0.101529 0.0 0.0 NaN \n", "142 MV Load 1 6 0.5 0.101529 0.0 0.0 NaN \n", "143 MV Load 2 48 0.5 0.101529 0.0 0.0 NaN \n", "144 MV Load 3 52 0.5 0.101529 0.0 0.0 NaN \n", "145 MV Load 4 55 0.5 0.101529 0.0 0.0 NaN \n", "146 MV Load 5 235 0.5 0.101529 0.0 0.0 NaN \n", "\n", " scaling in_service type \n", "141 0.6 True MV Load \n", "142 0.6 True MV Load \n", "143 0.6 True MV Load \n", "144 0.6 True MV Load \n", "145 0.6 True MV Load \n", "146 0.6 True MV Load " ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.load[net.load.name.str.startswith(\"MV\")]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "or to find all all loads that contain \"Load 0\" in their name:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namebusp_mwq_mvarconst_z_percentconst_i_percentsn_mvascalingin_servicetype
0LV Load 01030.250.0507650.00.0NaN0.6TrueMV/LV Station
141MV Load 02150.500.1015290.00.0NaN0.6TrueMV Load
\n", "
" ], "text/plain": [ " name bus p_mw q_mvar const_z_percent const_i_percent sn_mva \\\n", "0 LV Load 0 103 0.25 0.050765 0.0 0.0 NaN \n", "141 MV Load 0 215 0.50 0.101529 0.0 0.0 NaN \n", "\n", " scaling in_service type \n", "0 0.6 True MV/LV Station \n", "141 0.6 True MV Load " ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.load[net.load.name.str.contains(\"Load 0\")]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The isin function allows you to check if certain values are contained in a list, for example:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namefrom_busto_bus
165Line 1653986
\n", "
" ], "text/plain": [ " name from_bus to_bus\n", "165 Line 165 39 86" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lines = net.line[net.line.from_bus.isin([39,72])]\n", "lines[[\"name\", \"from_bus\", \"to_bus\"]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "returns the names of all lines that are connected to one of the buses 39 or 72 at the from bus." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also combine certain queries with the logical | (or) and & (and) for more complicated queries, for example:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namefrom_busto_bus
36Line 3628972
54Line 543072
162Line 1628039
165Line 1653986
\n", "
" ], "text/plain": [ " name from_bus to_bus\n", "36 Line 36 289 72\n", "54 Line 54 30 72\n", "162 Line 162 80 39\n", "165 Line 165 39 86" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lines = net.line[(net.line.from_bus.isin([39,72])) | (net.line.to_bus.isin([39,72]))]\n", "lines[[\"name\", \"from_bus\", \"to_bus\"]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "returns the names of all lines that are connected to one of the buses 39 or 72 at the from bus or to bus." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Please be aware that the boolean query always returns a copy of the dataframe. Lets say you want to set all medium voltage nodes out of service and try this:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "net.bus[net.bus.vn_kv > 25].in_service = False" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You will notice that your pandapower network table did not change:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0 True\n", "1 True\n", "2 True\n", "3 True\n", "4 True\n", "Name: in_service, dtype: bool" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.bus.in_service.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is because the query returns a copy and you change the value in this copy in the same way like the following code:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 False\n", "1 False\n", "2 False\n", "3 False\n", "4 False\n", "Name: in_service, dtype: bool\n", "0 True\n", "1 True\n", "2 True\n", "3 True\n", "4 True\n", "Name: in_service, dtype: bool\n" ] } ], "source": [ "bus_table = net.bus[net.bus.vn_kv < 25]\n", "bus_table.in_service = False\n", "\n", "#Output\n", "print(bus_table.in_service.head())\n", "# but still:\n", "print(net.bus.in_service.head())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here bus_table is a copy of a part of the net.bus table. In the example above, you changed that copy, but not the original table. That is why you need to save the index of the copied bus table and than change the value of the original table:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 False\n", "1 False\n", "2 False\n", "3 False\n", "4 False\n", "Name: in_service, dtype: bool\n" ] } ], "source": [ "#Get index and store in ns_nodes\n", "ns_nodes = net.bus[net.bus.vn_kv < 25].index\n", "\n", "#Change all values where index is in ns_nodes\n", "net.bus.in_service.loc[ns_nodes] = False\n", "\n", "#Output\n", "print(net.bus.in_service.head())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Extending and Customizing the Framework\n", "\n", "One of the benefits of pandapower is its easy to customize and extend datastructure. For pandapower to be able to run a loadflow, the standard parameters of the datastructure have to be defined. However, you can extend the pandapower datastructure any way you like." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Say you have network that is partitioned in three zones A, B and C: " ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0 A\n", "1 A\n", "2 A\n", "3 A\n", "4 A\n", "Name: zone, dtype: object" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "nr_buses = len(net.bus.index)\n", "net.bus.loc[net.bus.index[:nr_buses], \"zone\"] = \"A\"\n", "net.bus.loc[net.bus.index[nr_buses:], \"zone\"] = \"B\"\n", "net.bus.zone.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The goal is to apply a load scaling factor of 0.8 in Zone A and of 0.6 in Zone B:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namebusp_mwq_mvarconst_z_percentconst_i_percentsn_mvascalingin_servicetype
0LV Load 01030.250.0507650.00.0NaN0.8TrueMV/LV Station
1LV Load 11740.630.1279270.00.0NaN0.8TrueMV/LV Station
2LV Load 21940.250.0507650.00.0NaN0.8TrueMV/LV Station
3LV Load 3340.250.0507650.00.0NaN0.8TrueMV/LV Station
4LV Load 4760.250.0507650.00.0NaN0.8TrueMV/LV Station
\n", "
" ], "text/plain": [ " name bus p_mw q_mvar const_z_percent const_i_percent sn_mva \\\n", "0 LV Load 0 103 0.25 0.050765 0.0 0.0 NaN \n", "1 LV Load 1 174 0.63 0.127927 0.0 0.0 NaN \n", "2 LV Load 2 194 0.25 0.050765 0.0 0.0 NaN \n", "3 LV Load 3 34 0.25 0.050765 0.0 0.0 NaN \n", "4 LV Load 4 76 0.25 0.050765 0.0 0.0 NaN \n", "\n", " scaling in_service type \n", "0 0.8 True MV/LV Station \n", "1 0.8 True MV/LV Station \n", "2 0.8 True MV/LV Station \n", "3 0.8 True MV/LV Station \n", "4 0.8 True MV/LV Station " ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "for i, load in net.load.iterrows():\n", " if net.bus.zone.at[load.bus] == \"A\":\n", " net.load.scaling.at[i] = 0.8\n", " elif net.bus.zone.at[load.bus] == \"B\":\n", " net.load.scaling.at[i] = 0.6\n", "net.load.head(n=5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, you could also extend the line table to include the zone of each line like this:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namebusp_mwq_mvarconst_z_percentconst_i_percentsn_mvascalingin_servicetypezone
0LV Load 01030.250.0507650.00.0NaN0.8TrueMV/LV StationA
1LV Load 11740.630.1279270.00.0NaN0.8TrueMV/LV StationA
2LV Load 21940.250.0507650.00.0NaN0.8TrueMV/LV StationA
3LV Load 3340.250.0507650.00.0NaN0.8TrueMV/LV StationA
4LV Load 4760.250.0507650.00.0NaN0.8TrueMV/LV StationA
\n", "
" ], "text/plain": [ " name bus p_mw q_mvar const_z_percent const_i_percent sn_mva \\\n", "0 LV Load 0 103 0.25 0.050765 0.0 0.0 NaN \n", "1 LV Load 1 174 0.63 0.127927 0.0 0.0 NaN \n", "2 LV Load 2 194 0.25 0.050765 0.0 0.0 NaN \n", "3 LV Load 3 34 0.25 0.050765 0.0 0.0 NaN \n", "4 LV Load 4 76 0.25 0.050765 0.0 0.0 NaN \n", "\n", " scaling in_service type zone \n", "0 0.8 True MV/LV Station A \n", "1 0.8 True MV/LV Station A \n", "2 0.8 True MV/LV Station A \n", "3 0.8 True MV/LV Station A \n", "4 0.8 True MV/LV Station A " ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.scaling = 1.0 #reset\n", "net.load[\"zone\"] = net.bus.zone.loc[net.load.bus.values].values\n", "net.load.head(n=5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and then apply your function only to the lines in Zone A:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namebusp_mwq_mvarconst_z_percentconst_i_percentsn_mvascalingin_servicetypezone
0LV Load 01030.250.0507650.00.0NaN0.8TrueMV/LV StationA
1LV Load 11740.630.1279270.00.0NaN0.8TrueMV/LV StationA
2LV Load 21940.250.0507650.00.0NaN0.8TrueMV/LV StationA
3LV Load 3340.250.0507650.00.0NaN0.8TrueMV/LV StationA
4LV Load 4760.250.0507650.00.0NaN0.8TrueMV/LV StationA
\n", "
" ], "text/plain": [ " name bus p_mw q_mvar const_z_percent const_i_percent sn_mva \\\n", "0 LV Load 0 103 0.25 0.050765 0.0 0.0 NaN \n", "1 LV Load 1 174 0.63 0.127927 0.0 0.0 NaN \n", "2 LV Load 2 194 0.25 0.050765 0.0 0.0 NaN \n", "3 LV Load 3 34 0.25 0.050765 0.0 0.0 NaN \n", "4 LV Load 4 76 0.25 0.050765 0.0 0.0 NaN \n", "\n", " scaling in_service type zone \n", "0 0.8 True MV/LV Station A \n", "1 0.8 True MV/LV Station A \n", "2 0.8 True MV/LV Station A \n", "3 0.8 True MV/LV Station A \n", "4 0.8 True MV/LV Station A " ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.load.loc[net.load[net.load.zone==\"A\"].index, \"scaling\"] = 0.8\n", "net.load.loc[net.load[net.load.zone==\"B\"].index, \"scaling\"] = 0.6\n", "net.load.head(n=5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Of course for this simple example, the first solution would also be feasible. But the more complicated your code gets, the more important it is to customize the pandapower framework, so your code is more efficient as well as easier to read and debug." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Merging Dataframes\n", "\n", "Lets say you want to see the voltage at each bus together with the bus name. The voltage is stored in net.res_bus, whereas the name ist stored in net.bus. The two values are in different tables, but they have the same index, which is why you can easily merge them into a new shared dataframe:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namevm_pu
0Bus 01.009473
1Bus 11.009354
2Bus 21.010306
3Bus 31.008611
4Bus 41.010021
5Bus 51.015559
6Bus 61.023551
7Bus 71.015590
\n", "
" ], "text/plain": [ " name vm_pu\n", "0 Bus 0 1.009473\n", "1 Bus 1 1.009354\n", "2 Bus 2 1.010306\n", "3 Bus 3 1.008611\n", "4 Bus 4 1.010021\n", "5 Bus 5 1.015559\n", "6 Bus 6 1.023551\n", "7 Bus 7 1.015590" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net = nw.mv_oberrhein()\n", "pp.runpp(net) #Run a load flow calculation to get result tables\n", "\n", "#Store bus name with bus voltage result of loadflow calculation in bus_results\n", "#axis={horizontal=0, vertical=1}: axis to concatenate along\n", "bus_results = pd.concat([net.bus.name, net.res_bus.vm_pu], axis=1)\n", "\n", "#Output\n", "bus_results.head(n=8)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now bus results shows you the name and the voltage of each bus in one dataframe." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you want to see the bus voltage of all loads, you can also merge the bus and the load dataframe. They do not share an index, but rather the index of the bus is given in the “bus” column of the load table. In that case you can merge the dataframes like this: (for more information about the arguments see [pandas merging documentation] [pandas_merg])\n", "[pandas_merg]: http://pandas.pydata.org/pandas-docs/stable/merging.html " ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "scrolled": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
vm_puva_degreep_mw_xq_mvar_xnamebusp_mw_yq_mvar_yconst_z_percentconst_i_percentsn_mvascalingin_servicetype
671.009473-6.7789210.240.048734LV Load 6700.400.0812230.00.0NaN0.6TrueMV/LV Station
171.009354-6.7835260.150.030459LV Load 1710.250.0507650.00.0NaN0.6TrueMV/LV Station
1281.010306-6.7530590.150.030459LV Load 12820.250.0507650.00.0NaN0.6TrueMV/LV Station
731.008611-6.8074920.150.030459LV Load 7330.250.0507650.00.0NaN0.6TrueMV/LV Station
531.010021-6.7614930.150.030459LV Load 5340.250.0507650.00.0NaN0.6TrueMV/LV Station
611.015559-6.3211470.150.030459LV Load 6150.250.0507650.00.0NaN0.6TrueMV/LV Station
1421.023551-5.4785060.300.060918MV Load 160.500.1015290.00.0NaN0.6TrueMV Load
1011.008905-6.7970720.240.048734LV Load 10180.400.0812230.00.0NaN0.6TrueMV/LV Station
\n", "
" ], "text/plain": [ " vm_pu va_degree p_mw_x q_mvar_x name bus p_mw_y \\\n", "67 1.009473 -6.778921 0.24 0.048734 LV Load 67 0 0.40 \n", "17 1.009354 -6.783526 0.15 0.030459 LV Load 17 1 0.25 \n", "128 1.010306 -6.753059 0.15 0.030459 LV Load 128 2 0.25 \n", "73 1.008611 -6.807492 0.15 0.030459 LV Load 73 3 0.25 \n", "53 1.010021 -6.761493 0.15 0.030459 LV Load 53 4 0.25 \n", "61 1.015559 -6.321147 0.15 0.030459 LV Load 61 5 0.25 \n", "142 1.023551 -5.478506 0.30 0.060918 MV Load 1 6 0.50 \n", "101 1.008905 -6.797072 0.24 0.048734 LV Load 101 8 0.40 \n", "\n", " q_mvar_y const_z_percent const_i_percent sn_mva scaling in_service \\\n", "67 0.081223 0.0 0.0 NaN 0.6 True \n", "17 0.050765 0.0 0.0 NaN 0.6 True \n", "128 0.050765 0.0 0.0 NaN 0.6 True \n", "73 0.050765 0.0 0.0 NaN 0.6 True \n", "53 0.050765 0.0 0.0 NaN 0.6 True \n", "61 0.050765 0.0 0.0 NaN 0.6 True \n", "142 0.101529 0.0 0.0 NaN 0.6 True \n", "101 0.081223 0.0 0.0 NaN 0.6 True \n", "\n", " type \n", "67 MV/LV Station \n", "17 MV/LV Station \n", "128 MV/LV Station \n", "73 MV/LV Station \n", "53 MV/LV Station \n", "61 MV/LV Station \n", "142 MV Load \n", "101 MV/LV Station " ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "load_bus_results = pd.merge(net.res_bus, net.load, left_index=True, right_on=\"bus\")\n", "\n", "#Output\n", "load_bus_results.head(n=8)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here net.res_bus is the left and net.load is the right dataframe. To merge the dataframe into one, the index of the left dataframe (left_index=True) and the bus column of the right dataframe (right_on=”bus”) are used. The resulting dataframe shows you the load table with the bus results of the respective load buses." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you want to know the maximum voltage at a bus that has a load connected to it, you could use:" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1.0235513513298844" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "max_load_voltage = pd.merge(net.res_bus, net.load, left_index=True, right_on=\"bus\").vm_pu.max()\n", "\n", "#Output\n", "max_load_voltage" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "or alternatively with a boolean mask:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1.0235513513298844" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "max_load_voltage = net.res_bus[net.bus.index.isin(net.load.bus.values)].vm_pu.max()\n", "\n", "#Output\n", "max_load_voltage" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this simple case, the boolean mask solution is about 5 times faster than merging the dataframes, so only use merging for more complicated operations or if runtime is not important (e.g. debugging)." ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13" } }, "nbformat": 4, "nbformat_minor": 1 }