diff --git a/.gitignore b/.gitignore
index c877bae..bb1145a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,8 +36,6 @@ pip-delete-this-directory.txt
 
 # Doc
 doc/temp/*
-!doc/temp/windpowerlib.wind_farm.WindFarm.rst
-!doc/temp/windpowerlib.wind_turbine_cluster.WindTurbineCluster.rst
 .doctrees
 .buildinfo
 
diff --git a/doc/conf.py b/doc/conf.py
index 31f0169..e6bd938 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -157,7 +157,7 @@ html_theme = 'sphinx_rtd_theme'
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = []
 
 # Add any extra paths that contain custom files (such as robots.txt or
 # .htaccess) here, relative to this directory. These files are copied
diff --git a/doc/getting_started.rst b/doc/getting_started.rst
index 12f38cc..9e3104d 100644
--- a/doc/getting_started.rst
+++ b/doc/getting_started.rst
@@ -55,9 +55,9 @@ The basic usage of the windpowerlib is shown in the ModelChain example that is a
 To run the example you need the example weather and turbine data used:
 
  * :download:`Example weather data file <../example/weather.csv>`
- * :download:`Example power curve data file <../example/data/example_power_curves.csv>`
- * :download:`Example power coefficient curve data file <../example/data/example_power_coefficient_curves.csv>`
- * :download:`Example nominal power data file <../example/data/example_turbine_data.csv>`
+ * :download:`Example power curve data file <../example/data/power_curves.csv>`
+ * :download:`Example power coefficient curve data file <../example/data/power_coefficient_curves.csv>`
+ * :download:`Example nominal power data file <../example/data/turbine_data.csv>`
 
 Furthermore, you have to install the windpowerlib and to run the notebook you also need to install `notebook` using pip3. To launch jupyter notebook type ``jupyter notebook`` in the terminal.
 This will open a browser window. Navigate to the directory containing the notebook to open it. See the jupyter notebook quick start guide for more information on `how to install <http://jupyter-notebook-beginner-guide.readthedocs.io/en/latest/install.html>`_ and
diff --git a/doc/modules.rst b/doc/modules.rst
index 36b71a3..eac8a60 100644
--- a/doc/modules.rst
+++ b/doc/modules.rst
@@ -63,7 +63,6 @@ Wind turbine data
 Functions and methods to obtain the nominal power as well as 
 power curve or power coefficient curve needed by the :py:class:`~.wind_turbine.WindTurbine` class.
 
-
 .. autosummary::
    :toctree: temp/
 
@@ -71,6 +70,19 @@ power curve or power coefficient curve needed by the :py:class:`~.wind_turbine.W
    wind_turbine.load_turbine_data_from_oedb
    wind_turbine.get_turbine_types
 
+.. _create_input_types_label:
+
+Data Container
+=====================
+
+Create data container to be used as an input in classes und functions.
+
+.. autosummary::
+   :toctree: temp/
+
+   wind_turbine.WindTurbineGroup
+   wind_turbine.WindTurbine.to_group
+
 .. _wind_farm_label:
 
 Wind farm calculations
@@ -234,4 +246,4 @@ TurbineClusterModelChain example
 The ``turbine_cluster_modelchain_example`` consists of the following functions
 as well as it uses functions of the ``modelchain_example``.
 
-.. include:: example_2.rst
\ No newline at end of file
+.. include:: example_2.rst
diff --git a/doc/temp/windpowerlib.wind_farm.WindFarm.rst b/doc/temp/windpowerlib.wind_farm.WindFarm.rst
deleted file mode 100644
index af93b56..0000000
--- a/doc/temp/windpowerlib.wind_farm.WindFarm.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-windpowerlib.wind\_farm.WindFarm
-======================================
-
-.. currentmodule:: windpowerlib.wind_farm
-
-.. autoclass:: WindFarm
-   :members: 
-
diff --git a/doc/temp/windpowerlib.wind_turbine_cluster.WindTurbineCluster.rst b/doc/temp/windpowerlib.wind_turbine_cluster.WindTurbineCluster.rst
deleted file mode 100644
index 7b0d1dd..0000000
--- a/doc/temp/windpowerlib.wind_turbine_cluster.WindTurbineCluster.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-windpowerlib.wind\_turbine\_cluster.WindTurbineCluster
-======================================================
-
-.. currentmodule:: windpowerlib.wind_turbine_cluster
-
-.. autoclass:: WindTurbineCluster
-   :members: 
-
diff --git a/example/modelchain_example.ipynb b/example/modelchain_example.ipynb
index d3d9d9e..b7a9a47 100644
--- a/example/modelchain_example.ipynb
+++ b/example/modelchain_example.ipynb
@@ -157,7 +157,7 @@
    "source": [
     "To initialize a specific turbine you need a dictionary that contains the basic parameters. A turbine is defined by its nominal power, hub height, rotor diameter, and power or power coefficient curve.\n",
     "\n",
-    "There are three ways to initialize a WindTurbine object in the windpowerlib. You can either specify your own turbine, as done below for 'my_turbine', or fetch power and/or power coefficient curve data from data files provided by the windpowerlib, as done for the 'enercon_e126', or provide your turbine data in csv files as done for the 'dummy_turbine' with an example file.\n",
+    "There are three ways to initialize a WindTurbine object in the windpowerlib. You can either use turbine data from the OpenEnergy Database (oedb) turbine library that is provided along with the windpowerlib, as done for the 'enercon_e126', or specify your own turbine by directly providing a power (coefficient) curve, as done below for 'my_turbine', or provide your own turbine data in csv files, as done for 'dummy_turbine'.\n",
     "\n",
     "You can execute the following to get a table of all wind turbines for which power and/or power coefficient curves are provided."
    ]
@@ -228,28 +228,6 @@
     "print(df[df[\"turbine_type\"].str.contains(\"E-101\")])"
    ]
   },
-  {
-   "cell_type": "code",
-   "execution_count": 33,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# specification of own wind turbine (Note: power coefficient values and\n",
-    "# nominal power have to be in Watt)\n",
-    "my_turbine = {\n",
-    "    'name': 'myTurbine',\n",
-    "    'nominal_power': 3e6,  # in W\n",
-    "    'hub_height': 105,  # in m\n",
-    "    'rotor_diameter': 90,  # in m\n",
-    "    'power_curve': pd.DataFrame(\n",
-    "            data={'value': [p * 1000 for p in [\n",
-    "                      0.0, 26.0, 180.0, 1500.0, 3000.0, 3000.0]],  # in W\n",
-    "                  'wind_speed': [0.0, 3.0, 5.0, 10.0, 15.0, 25.0]})  # in m/s\n",
-    "    }  \n",
-    "# initialisze WindTurbine object\n",
-    "my_turbine = WindTurbine(**my_turbine)"
-   ]
-  },
   {
    "cell_type": "code",
    "execution_count": 6,
@@ -270,13 +248,10 @@
    "source": [
     "# specification of wind turbine where power curve is provided in the \n",
     "# oedb turbine library\n",
-    "# if you want to use the power coefficient curve change the value of\n",
-    "# 'power_coefficient_curve' to 'oedb'.\n",
+    "\n",
     "enercon_e126 = {\n",
     "        'turbine_type': 'E-126/4200',  # turbine type as in oedb turbine library\n",
-    "        'hub_height': 135,  # in m\n",
-    "        'rotor_diameter': 127,  # in m (only needs to be provided when calculating the power \n",
-    "                                # output using the power coefficient curve)\n",
+    "        'hub_height': 135  # in m\n",
     "    }\n",
     "# initialize WindTurbine object\n",
     "e126 = WindTurbine(**enercon_e126)"
diff --git a/example/modelchain_example.py b/example/modelchain_example.py
index 1948fed..d28f679 100644
--- a/example/modelchain_example.py
+++ b/example/modelchain_example.py
@@ -94,15 +94,15 @@ def initialize_wind_turbines():
     Initializes three :class:`~.wind_turbine.WindTurbine` objects.
 
     This function shows three ways to initialize a WindTurbine object. You can
-    either specify your own turbine by directly providing a power (coefficient)
-    curve, as done below for 'my_turbine', or provide your own turbine data in
-    csv files as done for 'dummy_turbine', or you can use power and/or power
-    coefficient curve data from the OpenEnergy Database
-    (oedb) turbine library that is provided along with the windpowerlib,
-    as done for the 'enercon_e126'. Execute
-    ``windpowerlib.wind_turbine.get_turbine_types()`` to get a table
-    including all wind turbines for which power and/or power coefficient curves
-    are provided.
+    either use turbine data from the OpenEnergy Database (oedb) turbine library
+    that is provided along with the windpowerlib, as done for the
+    'enercon_e126', or specify your own turbine by directly providing a power
+    (coefficient) curve, as done below for 'my_turbine', or provide your own
+    turbine data in csv files, as done for 'dummy_turbine'.
+
+    To get a list of all wind turbines for which power and/or power coefficient
+    curves are provided execute `
+    `windpowerlib.wind_turbine.get_turbine_types()``.
 
     Returns
     -------
@@ -112,6 +112,15 @@ def initialize_wind_turbines():
 
     """
 
+    # specification of wind turbine where data is provided in the oedb
+    # turbine library
+    enercon_e126 = {
+        'turbine_type': 'E-126/4200',  # turbine type as in register
+        'hub_height': 135  # in m
+    }
+    # initialize WindTurbine object
+    e126 = WindTurbine(**enercon_e126)
+
     # specification of own wind turbine (Note: power values and nominal power
     # have to be in Watt)
     my_turbine = {
@@ -137,18 +146,6 @@ def initialize_wind_turbines():
     # initialize WindTurbine object
     dummy_turbine = WindTurbine(**dummy_turbine)
 
-    # specification of wind turbine where power curve is provided in the oedb
-    # turbine library
-    # if you want to use the power coefficient curve change the value of
-    # 'power_coefficient_curve' to True.
-    enercon_e126 = {
-        'turbine_type': 'E-126/4200',  # turbine type as in register
-        'hub_height': 135,  # in m
-        'rotor_diameter': 127,  # in m
-    }
-    # initialize WindTurbine object
-    e126 = WindTurbine(**enercon_e126)
-
     return my_turbine, e126, dummy_turbine
 
 
diff --git a/example/turbine_cluster_modelchain_example.ipynb b/example/turbine_cluster_modelchain_example.ipynb
index 74d87d2..a60f45b 100644
--- a/example/turbine_cluster_modelchain_example.ipynb
+++ b/example/turbine_cluster_modelchain_example.ipynb
@@ -29,6 +29,8 @@
     "__copyright__ = \"Copyright oemof developer group\"\n",
     "__license__ = \"GPLv3\"\n",
     "\n",
+    "import pandas as pd\n",
+    "\n",
     "import modelchain_example as mc_e\n",
     "from windpowerlib import TurbineClusterModelChain, WindTurbineCluster, WindFarm\n",
     "\n",
@@ -92,19 +94,19 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# specification of wind farm\n",
-    "example_farm_data = {\n",
-    "    'name': 'example_farm',\n",
-    "    'wind_turbine_fleet': [{'wind_turbine': my_turbine,  # as windpowerlib.WindTurbine\n",
-    "                            'number_of_turbines': 6},  # number of `my_turbine` turbines in farm (float \n",
-    "                                                       # values are possible as well)\n",
-    "                           {'wind_turbine': e126,\n",
-    "                            'total_capacity': 12.6e6}  # installed capacity of `my_turbine` turbines \n",
-    "                                                       # in farm in Watt\n",
-    "                           ]}\n",
-    "\n",
+    "# specification of wind farm data where turbine fleet is provided in a\n",
+    "# pandas.DataFrame\n",
+    "# for each turbine type you can either specify the number of turbines of\n",
+    "# that type in the wind farm (float values are possible as well) or the\n",
+    "# total installed capacity of that turbine type in W\n",
+    "wind_turbine_fleet = pd.DataFrame(\n",
+    "        {'wind_turbine': [my_turbine, e126],  # as windpowerlib.WindTurbine\n",
+    "         'number_of_turbines': [6, None],\n",
+    "         'total_capacity': [None, 12.6e6]}\n",
+    "    )\n",
     "# initialize WindFarm object\n",
-    "example_farm = WindFarm(**example_farm_data)"
+    "example_farm = WindFarm(name='example_farm',\n",
+    "                        wind_turbine_fleet=wind_turbine_fleet)"
    ]
   },
   {
@@ -128,13 +130,12 @@
     }
    ],
    "source": [
-    "# specification of wind farm (2) with a wind farm efficiency\n",
+    "# specification of wind farm data (2) containing a wind farm efficiency\n",
+    "# wind turbine fleet is provided using the to_group function\n",
     "example_farm_2_data = {\n",
     "    'name': 'example_farm_2',\n",
-    "    'wind_turbine_fleet': [{'wind_turbine': my_turbine,\n",
-    "                            'number_of_turbines': 6},\n",
-    "                           {'wind_turbine': e126,\n",
-    "                            'number_of_turbines': 3}],\n",
+    "    'wind_turbine_fleet': [my_turbine.to_group(6),\n",
+    "                           e126.to_group(total_capacity=12.6e6)],\n",
     "    'efficiency': 0.9}\n",
     "\n",
     "# initialize WindFarm object\n",
@@ -176,7 +177,7 @@
     "\n",
     "The TurbineClusterModelChain is a class that provides all necessary steps to calculate the power output of a wind farm or wind turbine cluster. \n",
     "\n",
-    "Like the ModelChain (see [basic example](modelchain_example.ipynb)) you can use the TurbineClusterModelChain with default parameters as shown in this example for the wind farm or specify custom parameters as done here for the cluster.\n",
+    "Like the ModelChain (see [basic example](modelchain_example_notebook.ipynb)) you can use the TurbineClusterModelChain with default parameters as shown in this example for the wind farm or specify custom parameters as done here for the cluster.\n",
     "If you use the 'run_model' method first the aggregated power curve and the mean hub height of the wind farm/cluster is calculated, then inherited functions of the ModelChain are used to calculate the wind speed and density (if necessary) at hub height. After that, depending on the parameters, wake losses are applied and at last the power output is calculated."
    ]
   },
diff --git a/example/turbine_cluster_modelchain_example.py b/example/turbine_cluster_modelchain_example.py
index 4112f6b..6afeb27 100644
--- a/example/turbine_cluster_modelchain_example.py
+++ b/example/turbine_cluster_modelchain_example.py
@@ -12,6 +12,8 @@ of wind turbines, are imported and used without further explanations.
 __copyright__ = "Copyright oemof developer group"
 __license__ = "GPLv3"
 
+import pandas as pd
+
 try:
     from matplotlib import pyplot as plt
 except ImportError:
@@ -53,28 +55,26 @@ def initialize_wind_farms(my_turbine, e126):
 
     """
 
-    # specification of wind farm data
-    example_farm_data = {
-        'name': 'example_farm',
-        'wind_turbine_fleet': [
-            {'wind_turbine': my_turbine,  # as windpowerlib.WindTurbine
-             'number_of_turbines': 6},  # number of `my_turbine` turbines in
-                                        # farm (float values are possible as
-                                        # well)
-            {'wind_turbine': e126,
-             'total_capacity': 12.6e6  # installed capacity of `my_turbine`
-                                       # turbines in farm in Watt
-             }]}
+    # specification of wind farm data where turbine fleet is provided in a
+    # pandas.DataFrame
+    # for each turbine type you can either specify the number of turbines of
+    # that type in the wind farm (float values are possible as well) or the
+    # total installed capacity of that turbine type in W
+    wind_turbine_fleet = pd.DataFrame(
+        {'wind_turbine': [my_turbine, e126],  # as windpowerlib.WindTurbine
+         'number_of_turbines': [6, None],
+         'total_capacity': [None, 12.6e6]}
+    )
     # initialize WindFarm object
-    example_farm = WindFarm(**example_farm_data)
+    example_farm = WindFarm(name='example_farm',
+                            wind_turbine_fleet=wind_turbine_fleet)
 
     # specification of wind farm data (2) containing a wind farm efficiency
+    # wind turbine fleet is provided using the to_group function
     example_farm_2_data = {
         'name': 'example_farm_2',
-        'wind_turbine_fleet': [{'wind_turbine': my_turbine,
-                                'number_of_turbines': 6},
-                               {'wind_turbine': e126,
-                                'number_of_turbines': 3}],
+        'wind_turbine_fleet': [my_turbine.to_group(6),
+                               e126.to_group(total_capacity=12.6e6)],
         'efficiency': 0.9}
     # initialize WindFarm object
     example_farm_2 = WindFarm(**example_farm_2_data)
diff --git a/windpowerlib/wind_farm.py b/windpowerlib/wind_farm.py
index 4f8f499..c037b91 100644
--- a/windpowerlib/wind_farm.py
+++ b/windpowerlib/wind_farm.py
@@ -21,13 +21,29 @@ class WindFarm(object):
 
     Parameters
     ----------
-    wind_turbine_fleet : :pandas:`pandas.DataFrame<frame>` or list(dict)
-        Wind turbines of wind farm. DataFrame/Dictionaries must have
-        'wind_turbine' containing a :class:`~.wind_turbine.WindTurbine` object
-        and either 'number_of_turbines' (number of wind turbines of the same
-        turbine type in the wind farm, can be a float) or 'total_capacity'
-        (installed capacity of wind turbines of the same turbine type in the
-        wind farm) as columns/keys. See example below.
+    wind_turbine_fleet : :pandas:`pandas.DataFrame<frame>` or list(:class:`~windpowerlib.wind_turbine.WindTurbineGroup`)
+        The wind turbine fleet specifies the turbine types in the wind farm and
+        their corresponding number or total installed capacity. There are
+        different options to provide the wind turbine fleet (see also examples
+        below):
+
+        * :pandas:`pandas.DataFrame<frame>` -
+          DataFrame must have columns 'wind_turbine' containing a
+          :class:`~.wind_turbine.WindTurbine` object and either
+          'number_of_turbines' (number of wind turbines of the same turbine
+          type in the wind farm, can be a float) or 'total_capacity'
+          (installed capacity of wind turbines of the same turbine type in the
+          wind farm).
+
+        * list(:class:`~windpowerlib.wind_turbine.WindTurbineGroup`) -
+          A :class:`~windpowerlib.wind_turbine.WindTurbineGroup` can be created
+          from a :class:`~windpowerlib.wind_turbine.WindTurbine` using the
+          :func:`~windpowerlib.wind_turbine.WindTurbine.to_group` method.
+
+        * list(dict) -
+          It is still possible to use a list of dictionaries (see example) but
+          we recommend to use one of the other options above.
+
     efficiency : float or :pandas:`pandas.DataFrame<frame>` or None (optional)
         Efficiency of the wind farm. Provide as either constant (float) or
         power efficiency curve (pd.DataFrame) containing 'wind_speed' and
@@ -38,11 +54,11 @@ class WindFarm(object):
 
     Attributes
     ----------
-    wind_turbine_fleet : list(dict)
-        Wind turbines of wind farm. Dictionaries must have 'wind_turbine'
+    wind_turbine_fleet : :pandas:`pandas.DataFrame<frame>`
+        Wind turbines of wind farm. DataFrame must have 'wind_turbine'
         (contains a :class:`~.wind_turbine.WindTurbine` object) and
         'number_of_turbines' (number of wind turbines of the same turbine type
-        in the wind farm) as keys.
+        in the wind farm) as columns.
     efficiency : float or :pandas:`pandas.DataFrame<frame>` or None
         Efficiency of the wind farm. Either constant (float) power efficiency
         curve (pd.DataFrame) containing 'wind_speed' and 'efficiency'
@@ -53,9 +69,6 @@ class WindFarm(object):
     hub_height : float
         The calculated mean hub height of the wind farm. See
         :py:func:`mean_hub_height` for more information.
-    nominal_power : float
-        The nominal power is the sum of the nominal power of all turbines in
-        the wind farm in W.
     power_curve : :pandas:`pandas.DataFrame<frame>` or None
         The calculated power curve of the wind farm. See
         :py:func:`assign_power_curve` for more information.
@@ -75,25 +88,31 @@ class WindFarm(object):
     ...     'turbine_type': 'V90/2000',
     ...     'nominal_power': 2e6}
     >>> v90 = WindTurbine(**vestasV90)
-    >>> # turbine fleet as DataFrame with number of turbines provided
+    >>> # turbine fleet as DataFrame
     >>> wind_turbine_fleet = pd.DataFrame(
     ...     {'wind_turbine': [e126, v90],
-    ...      'number_of_turbines': [6, 3]})
-    >>> example_farm = wind_farm.WindFarm(wind_turbine_fleet)
+    ...      'number_of_turbines': [6, None],
+    ...      'total_capacity': [None, 3 * 2e6]})
+    >>> example_farm = wind_farm.WindFarm(wind_turbine_fleet, name='my_farm')
+    >>> print(example_farm.nominal_power)
+    31200000.0
+    >>> # turbine fleet as a list of WindTurbineGroup objects using the
+    >>> # 'to_group' method.
+    >>> wind_turbine_fleet = [e126.to_group(6),
+    ...                       v90.to_group(total_capacity=3 * 2e6)]
+    >>> example_farm = wind_farm.WindFarm(wind_turbine_fleet, name='my_farm')
     >>> print(example_farm.nominal_power)
     31200000.0
-    >>> # turbine fleet as list with total capacity of each turbine type
-    >>> # provided
+    >>> # turbine fleet as list of dictionaries (not recommended)
     >>> example_farm_data = {
-    ...    'name': 'example_farm',
+    ...    'name': 'my_farm',
     ...    'wind_turbine_fleet': [{'wind_turbine': e126,
-    ...                            'total_capacity': 6 * 4.2e6},
+    ...                            'number_of_turbines': 6},
     ...                           {'wind_turbine': v90,
     ...                            'total_capacity': 3 * 2e6}]}
     >>> example_farm = wind_farm.WindFarm(**example_farm_data)
     >>> print(example_farm.nominal_power)
     31200000.0
-
     """
 
     def __init__(self, wind_turbine_fleet, efficiency=None, name='', **kwargs):
@@ -120,10 +139,7 @@ class WindFarm(object):
         """
         # convert list to dataframe if necessary
         if isinstance(self.wind_turbine_fleet, list):
-            try:
-                self.wind_turbine_fleet = pd.DataFrame(self.wind_turbine_fleet)
-            except:
-                raise ValueError("Wind turbine fleet not provided properly.")
+            self.wind_turbine_fleet = pd.DataFrame(self.wind_turbine_fleet)
 
         # check wind turbines
         try:
@@ -133,8 +149,8 @@ class WindFarm(object):
                         'Wind turbine must be provided as WindTurbine object '
                         'but was provided as {}.'.format(type(turbine)))
         except KeyError:
-            raise ValueError('Missing wind_turbine key/column in '
-                             'wind_turbine_fleet parameter.')
+            raise KeyError('Missing wind_turbine key/column in '
+                           'wind_turbine_fleet parameter.')
 
         # add columns for number of turbines and total capacity if they don't
         # yet exist
@@ -159,7 +175,7 @@ class WindFarm(object):
                 else:
                     self.wind_turbine_fleet.loc[ix, 'number_of_turbines'] = \
                         number_of_turbines
-            except:
+            except TypeError:
                 raise ValueError(msg.format(row['wind_turbine']))
 
         # calculate total capacity if necessary and check that total capacity
@@ -170,7 +186,7 @@ class WindFarm(object):
                     self.wind_turbine_fleet.loc[ix, 'total_capacity'] = \
                         row['number_of_turbines'] * \
                         row['wind_turbine'].nominal_power
-                except:
+                except TypeError:
                     raise ValueError(
                         'Total capacity of turbines of type {turbine} cannot '
                         'be deduced. Please check if the nominal power of the '
@@ -201,14 +217,7 @@ class WindFarm(object):
     @property
     def nominal_power(self):
         r"""
-        The nominal power of the wind farm.
-
-        See :attr:`~.wind_farm.WindFarm.nominal_power` for further information.
-
-        Parameters
-        -----------
-        nominal_power : float
-            Nominal power of the wind farm in W.
+        The nominal power is the sum of the nominal power of all turbines.
 
         Returns
         -------
diff --git a/windpowerlib/wind_turbine.py b/windpowerlib/wind_turbine.py
index 4388cd4..617a45a 100644
--- a/windpowerlib/wind_turbine.py
+++ b/windpowerlib/wind_turbine.py
@@ -14,6 +14,7 @@ import warnings
 import requests
 import os
 from windpowerlib.tools import WindpowerlibUserWarning
+from typing import NamedTuple
 
 
 class WindTurbine(object):
@@ -113,7 +114,6 @@ class WindTurbine(object):
     18000.0
     >>> print(e_t_1.nominal_power)
     1500000.0
-
     """
 
     def __init__(self, hub_height, nominal_power=None, path='oedb',
@@ -220,6 +220,96 @@ class WindTurbine(object):
 
         return turbine_repr
 
+    def to_group(self, number_turbines=None, total_capacity=None):
+        r"""
+        Creates a :class:`~windpowerlib.wind_turbine.WindTurbineGroup`, a
+        NamedTuple data container with the fields 'number_of_turbines' and
+        'wind_turbine'. If no parameter is passed the number of turbines is
+        set to one.
+
+        It can be used to calculate the number of turbines for a given total
+        capacity or to create a namedtuple that can be used to define a
+        :class:`~windpowerlib.wind_farm.WindFarm` object.
+
+        Parameters
+        ----------
+        number_turbines : float
+            Number of turbines of the defined type. Default: 1
+        total_capacity : float
+            Total capacity of the group of wind turbines of the same type.
+
+        Returns
+        -------
+        WindTurbineGroup
+            A namedtuple with two fields: 'number_of_turbines' and
+            'wind_turbine'.
+
+        Examples
+        --------
+        >>> from windpowerlib import WindTurbine
+        >>> enerconE126 = {
+        ...    'hub_height': 135,
+        ...    'turbine_type': 'E-126/4200'}
+        >>> e126 = WindTurbine(**enerconE126)
+        >>> e126.to_group(5).number_of_turbines
+        5
+        >>> e126.to_group().number_of_turbines
+        1
+        >>> e126.to_group(number_turbines=7).number_of_turbines
+        7
+        >>> e126.to_group(total_capacity=12600000).number_of_turbines
+        3.0
+        >>> e126.to_group(total_capacity=14700000).number_of_turbines
+        3.5
+        >>> e126.to_group(total_capacity=12600000).wind_turbine.nominal_power
+        4200000.0
+        >>> type(e126.to_group(5))
+        <class 'windpowerlib.wind_turbine.WindTurbineGroup'>
+        >>> e126.to_group(5)  # doctest: +NORMALIZE_WHITESPACE
+        WindTurbineGroup(wind_turbine=Wind turbine: E-126/4200 ['nominal
+        power=4200000.0 W', 'hub height=135 m', 'rotor diameter=127.0 m',
+        'power_coefficient_curve=True', 'power_curve=True'],
+        number_of_turbines=5)
+        """
+
+        if number_turbines is not None and total_capacity is not None:
+            raise ValueError("The 'number' and the 'total_capacity' parameter "
+                             "are mutually exclusive. Use just one of them.")
+        elif total_capacity is not None:
+            number_turbines = total_capacity / self.nominal_power
+        elif number_turbines is None:
+            number_turbines = 1
+
+        return WindTurbineGroup(
+            wind_turbine=self, number_of_turbines=number_turbines)
+
+
+# This is working for Python >= 3.5.
+# There a cleaner solutions for Python >= 3.6, once the support of 3.5 is
+# dropped: https://stackoverflow.com/a/50038614
+class WindTurbineGroup(NamedTuple('WindTurbineGroup', [
+        ('wind_turbine', WindTurbine), ('number_of_turbines', float)])):
+    """
+    A simple data container to define more than one turbine of the same type.
+    Use the :func:`~windpowerlib.wind_turbine.WindTurbine.to_group` method to
+    easily create a WindTurbineGroup from a
+    :class:`~windpowerlib.wind_turbine.WindTurbine` object.
+
+    Parameters
+    ----------
+    'wind_turbine' : WindTurbine
+        A WindTurbine object with all necessary attributes.
+    'number_of_turbines' : float
+        The number of turbines. The number is not restricted to integer values.
+    """
+    __slots__ = ()
+
+
+WindTurbineGroup.wind_turbine.__doc__ = (
+    'A :class:`~windpowerlib.wind_farm.WindTurbine` object.')
+WindTurbineGroup.number_of_turbines.__doc__ = (
+    'Number of turbines of type WindTurbine')
+
 
 def get_turbine_data_from_file(turbine_type, path):
     r"""
diff --git a/windpowerlib/wind_turbine_cluster.py b/windpowerlib/wind_turbine_cluster.py
index 2cc7728..fed0333 100644
--- a/windpowerlib/wind_turbine_cluster.py
+++ b/windpowerlib/wind_turbine_cluster.py
@@ -13,7 +13,6 @@ __license__ = "GPLv3"
 
 import numpy as np
 import pandas as pd
-import warnings
 
 
 class WindTurbineCluster(object):
@@ -36,9 +35,6 @@ class WindTurbineCluster(object):
     hub_height : float
         The calculated average hub height of the wind turbine cluster. See
         :py:func:`mean_hub_height` for more information.
-    nominal_power : float
-        The nominal power is the sum of the nominal power of all turbines in
-        the wind turbine cluster in W.
     power_curve : :pandas:`pandas.DataFrame<frame>` or None
         The calculated power curve of the wind turbine cluster. See
         :py:func:`assign_power_curve` for more information.
@@ -55,31 +51,24 @@ class WindTurbineCluster(object):
 
     def __repr__(self):
         if self.name is not '':
-            repr = 'Wind turbine cluster: {name}'.format(name=self.name)
+            wf_repr = 'Wind turbine cluster: {name}'.format(name=self.name)
         else:
             info = []
             for wind_farm in self.wind_farms:
                 info.append(wind_farm)
-            repr = r'Wind turbine cluster with: {info}'.format(info=info)
-        return repr
+            wf_repr = r'Wind turbine cluster with: {info}'.format(info=info)
+        return wf_repr
 
     @property
     def nominal_power(self):
         r"""
-        The nominal power of the wind turbine cluster.
-
-        See :attr:`~.wind_turbine_cluster.WindTurbineCluster.nominal_power`
-        for further information.
-
-        Parameters
-        -----------
-        nominal_power : float
-            Nominal power of the wind turbine cluster in w.
+        The nominal power is the sum of the nominal power of all turbines in
+        the wind turbine cluster.
 
         Returns
         -------
         float
-            Nominal power of the wind turbine cluster in w.
+            Nominal power of the wind turbine cluster in W.
 
         """
         if not self._nominal_power:

New class WindTurbineGroup
In PR #63 @uvchik suggested an additional class WindTurbineGroup in order to have a cleaner initialization of the WindFarm class. As that PR was intended to allow specifying a WindFarm with a total installed capacity per turbine type and the discussion about this new class went beyond that topic, I decided to move it to a new issue. Important comments about the new class from the PR are:
https://github.com/wind-python/windpowerlib/pull/63#issuecomment-517251617
https://github.com/wind-python/windpowerlib/pull/63#issuecomment-517624171
diff --git a/tests/test_wind_farm.py b/tests/test_wind_farm.py
index 724b928..f6ccb3b 100644
--- a/tests/test_wind_farm.py
+++ b/tests/test_wind_farm.py
@@ -48,10 +48,11 @@ class TestWindFarm:
 
     def test_initialization_1(self):
         """test catching error when wind_turbine_fleet not provided as list"""
-        msg = 'Wind turbine fleet not provided properly.'
+        msg = 'Wind turbine must be provided as WindTurbine object'
         with pytest.raises(ValueError, match=msg):
-            WindFarm(wind_turbine_fleet=[{'wind_turbine': 'turbine',
-                                          'number_of_turbines': 2}, 'dummy'])
+            WindFarm(wind_turbine_fleet={'wind_turbine': 'turbine',
+                                         'number_of_turbines': 2},
+                     name='dummy')
 
     def test_initialization_2(self):
         """test catching error when WindTurbine in wind_turbine_fleet
@@ -71,7 +72,7 @@ class TestWindFarm:
                                    WindTurbine(**self.test_turbine_2)],
                   'number_of_turbines': [3, 2]})
         msg = 'Missing wind_turbine key/column in wind_turbine_fleet'
-        with pytest.raises(ValueError, match=msg):
+        with pytest.raises(KeyError, match=msg):
             WindFarm(wind_turbine_fleet=wind_turbine_fleet)
 
     def test_initialization_4(self, recwarn):
@@ -144,4 +145,16 @@ class TestWindFarm:
                        'number_of_turbines': 2}]
         assert 'E-126/4200' in repr(WindFarm(wind_turbine_fleet=test_fleet))
 
-
+    def test_aggregation_of_power_curve_with_missing_power_curve(self):
+        """Test WindFarm.assign_power_curve() with missing power_curve."""
+        wt1 = WindTurbine(**self.test_turbine)
+        wt1.power_curve = None
+        wind_turbine_fleet = [
+            {'wind_turbine': wt1,
+             'number_of_turbines': 3},
+            {'wind_turbine': WindTurbine(**self.test_turbine_2),
+             'number_of_turbines': 2}]
+        windfarm = WindFarm(wind_turbine_fleet=wind_turbine_fleet)
+        msg = 'For an aggregated wind farm power curve each wind'
+        with pytest.raises(ValueError, match=msg):
+            windfarm.assign_power_curve()
diff --git a/tests/test_wind_turbine.py b/tests/test_wind_turbine.py
index b4846d0..e081882 100644
--- a/tests/test_wind_turbine.py
+++ b/tests/test_wind_turbine.py
@@ -10,18 +10,22 @@ import os
 from windpowerlib.tools import WindpowerlibUserWarning
 
 from windpowerlib.wind_turbine import (get_turbine_data_from_file, WindTurbine,
-                                       get_turbine_types,
+                                       get_turbine_types, WindTurbineGroup,
                                        load_turbine_data_from_oedb)
 
 
 class TestWindTurbine:
 
+    @classmethod
+    def setup_class(cls):
+        """Setup default values"""
+        cls.source = os.path.join(os.path.dirname(__file__), '../example/data')
+
     def test_warning(self, recwarn):
-        source = os.path.join(os.path.dirname(__file__), '../example/data')
         test_turbine_data = {'hub_height': 100,
                              'rotor_diameter': 80,
                              'turbine_type': 'turbine_not_in_file',
-                             'path': source}
+                             'path': self.source}
         assert(WindTurbine(**test_turbine_data).power_curve is None)
         assert recwarn.pop(WindpowerlibUserWarning)
 
@@ -42,6 +46,7 @@ class TestWindTurbine:
 
     def test_wrong_url_load_turbine_data(self):
         """Load turbine data from oedb."""
+
         with pytest.raises(ConnectionError,
                            match="Database connection not successful"):
             load_turbine_data_from_oedb('wrong_schema')
@@ -67,4 +72,28 @@ class TestWindTurbine:
                              'turbine_type': 'test_type',
                              'power_coefficient_curve': 'string'}
         with pytest.raises(TypeError):
-            WindTurbine(**test_turbine_data)
\ No newline at end of file
+            WindTurbine(**test_turbine_data)
+
+    def test_to_group_method(self):
+        example_turbine = {
+           'hub_height': 100,
+           'rotor_diameter': 70,
+           'turbine_type': 'DUMMY 3',
+           'path': self.source}
+        e_t_1 = WindTurbine(**example_turbine)
+        assert(isinstance(e_t_1.to_group(), WindTurbineGroup))
+        assert(e_t_1.to_group(5).number_of_turbines == 5)
+        assert(e_t_1.to_group(number_turbines=5).number_of_turbines == 5)
+        assert(e_t_1.to_group(total_capacity=3e6).number_of_turbines == 2.0)
+
+    def test_wrongly_defined_to_group_method(self):
+        example_turbine = {
+           'hub_height': 100,
+           'rotor_diameter': 70,
+           'turbine_type': 'DUMMY 3',
+           'path': self.source}
+        e_t_1 = WindTurbine(**example_turbine)
+        with pytest.raises(ValueError,
+                           match="The 'number' and the 'total_capacity' "
+                                 "parameter are mutually exclusive."):
+            e_t_1.to_group(5, 3000)

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov
3.7
doc/requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
aiofiles==22.1.0
aiosqlite==0.19.0
alabaster==0.7.13
anyio==3.7.1
argon2-cffi==23.1.0
argon2-cffi-bindings==21.2.0
arrow==1.2.3
attrs==24.2.0
Babel==2.14.0
backcall==0.2.0
beautifulsoup4==4.13.3
bleach==6.0.0
cached-property==1.5.2
certifi @ file:///croot/certifi_1671487769961/work/certifi
cffi==1.15.1
charset-normalizer==3.4.1
comm==0.1.4
coverage==7.2.7
debugpy==1.7.0
decorator==5.1.1
defusedxml==0.7.1
docutils==0.19
entrypoints==0.4
exceptiongroup==1.2.2
fastjsonschema==2.21.1
fqdn==1.5.1
idna==3.10
imagesize==1.4.1
importlib-metadata==6.7.0
importlib-resources==5.12.0
iniconfig==2.0.0
ipykernel==6.16.2
ipython==7.34.0
ipython-genutils==0.2.0
ipywidgets==8.1.5
isoduration==20.11.0
jedi==0.19.2
Jinja2==3.1.6
json5==0.9.16
jsonpointer==3.0.0
jsonschema==4.17.3
jupyter==1.1.1
jupyter-console==6.6.3
jupyter-events==0.6.3
jupyter-server==1.24.0
jupyter-ydoc==0.2.5
jupyter_client==7.4.9
jupyter_core==4.12.0
jupyter_server_fileid==0.9.3
jupyter_server_ydoc==0.8.0
jupyterlab==3.6.8
jupyterlab-pygments==0.2.2
jupyterlab_server==2.24.0
jupyterlab_widgets==3.0.13
MarkupSafe==2.1.5
matplotlib-inline==0.1.6
mistune==3.0.2
nbclassic==1.2.0
nbclient==0.7.4
nbconvert==7.6.0
nbformat==5.8.0
nbsphinx==0.9.7
nest-asyncio==1.6.0
notebook==6.5.7
notebook_shim==0.2.4
numpy==1.21.6
packaging==24.0
pandas==0.25.3
pandocfilters==1.5.1
parso==0.8.4
pexpect==4.9.0
pickleshare==0.7.5
pkgutil_resolve_name==1.3.10
pluggy==1.2.0
prometheus-client==0.17.1
prompt_toolkit==3.0.48
psutil==7.0.0
ptyprocess==0.7.0
pycparser==2.21
Pygments==2.17.2
pyrsistent==0.19.3
pytest==7.4.4
pytest-cov==4.1.0
python-dateutil==2.9.0.post0
python-json-logger==3.0.1
pytz==2025.2
PyYAML==6.0.1
pyzmq==26.2.1
requests==2.31.0
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
Send2Trash==1.8.3
six==1.17.0
sniffio==1.3.1
snowballstemmer==2.2.0
soupsieve==2.4.1
Sphinx==5.3.0
sphinx-rtd-theme==2.0.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
terminado==0.17.1
tinycss2==1.2.1
tomli==2.0.1
tornado==6.2
traitlets==5.9.0
typing_extensions==4.7.1
uri-template==1.3.0
urllib3==2.0.7
wcwidth==0.2.13
webcolors==1.13
webencodings==0.5.1
websocket-client==1.6.1
widgetsnbextension==4.0.13
-e git+https://github.com/wind-python/windpowerlib.git@78ce14a2dd6c51640e932c6de429a16747468eb6#egg=windpowerlib
y-py==0.6.2
ypy-websocket==0.8.4
zipp==3.15.0

name: windpowerlib
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - aiofiles==22.1.0
      - aiosqlite==0.19.0
      - alabaster==0.7.13
      - anyio==3.7.1
      - argon2-cffi==23.1.0
      - argon2-cffi-bindings==21.2.0
      - arrow==1.2.3
      - attrs==24.2.0
      - babel==2.14.0
      - backcall==0.2.0
      - beautifulsoup4==4.13.3
      - bleach==6.0.0
      - cached-property==1.5.2
      - cffi==1.15.1
      - charset-normalizer==3.4.1
      - comm==0.1.4
      - coverage==7.2.7
      - debugpy==1.7.0
      - decorator==5.1.1
      - defusedxml==0.7.1
      - docutils==0.19
      - entrypoints==0.4
      - exceptiongroup==1.2.2
      - fastjsonschema==2.21.1
      - fqdn==1.5.1
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==6.7.0
      - importlib-resources==5.12.0
      - iniconfig==2.0.0
      - ipykernel==6.16.2
      - ipython==7.34.0
      - ipython-genutils==0.2.0
      - ipywidgets==8.1.5
      - isoduration==20.11.0
      - jedi==0.19.2
      - jinja2==3.1.6
      - json5==0.9.16
      - jsonpointer==3.0.0
      - jsonschema==4.17.3
      - jupyter==1.1.1
      - jupyter-client==7.4.9
      - jupyter-console==6.6.3
      - jupyter-core==4.12.0
      - jupyter-events==0.6.3
      - jupyter-server==1.24.0
      - jupyter-server-fileid==0.9.3
      - jupyter-server-ydoc==0.8.0
      - jupyter-ydoc==0.2.5
      - jupyterlab==3.6.8
      - jupyterlab-pygments==0.2.2
      - jupyterlab-server==2.24.0
      - jupyterlab-widgets==3.0.13
      - markupsafe==2.1.5
      - matplotlib-inline==0.1.6
      - mistune==3.0.2
      - nbclassic==1.2.0
      - nbclient==0.7.4
      - nbconvert==7.6.0
      - nbformat==5.8.0
      - nbsphinx==0.9.7
      - nest-asyncio==1.6.0
      - notebook==6.5.7
      - notebook-shim==0.2.4
      - numpy==1.21.6
      - packaging==24.0
      - pandas==0.25.3
      - pandocfilters==1.5.1
      - parso==0.8.4
      - pexpect==4.9.0
      - pickleshare==0.7.5
      - pkgutil-resolve-name==1.3.10
      - pluggy==1.2.0
      - prometheus-client==0.17.1
      - prompt-toolkit==3.0.48
      - psutil==7.0.0
      - ptyprocess==0.7.0
      - pycparser==2.21
      - pygments==2.17.2
      - pyrsistent==0.19.3
      - pytest==7.4.4
      - pytest-cov==4.1.0
      - python-dateutil==2.9.0.post0
      - python-json-logger==3.0.1
      - pytz==2025.2
      - pyyaml==6.0.1
      - pyzmq==26.2.1
      - requests==2.31.0
      - rfc3339-validator==0.1.4
      - rfc3986-validator==0.1.1
      - send2trash==1.8.3
      - six==1.17.0
      - sniffio==1.3.1
      - snowballstemmer==2.2.0
      - soupsieve==2.4.1
      - sphinx==5.3.0
      - sphinx-rtd-theme==2.0.0
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - terminado==0.17.1
      - tinycss2==1.2.1
      - tomli==2.0.1
      - tornado==6.2
      - traitlets==5.9.0
      - typing-extensions==4.7.1
      - uri-template==1.3.0
      - urllib3==2.0.7
      - wcwidth==0.2.13
      - webcolors==1.13
      - webencodings==0.5.1
      - websocket-client==1.6.1
      - widgetsnbextension==4.0.13
      - y-py==0.6.2
      - ypy-websocket==0.8.4
      - zipp==3.15.0
prefix: /opt/conda/envs/windpowerlib

tests/test_wind_farm.py::TestWindFarm::test_initialization_list tests/test_wind_farm.py::TestWindFarm::test_initialization_list_2 tests/test_wind_farm.py::TestWindFarm::test_initialization_dataframe tests/test_wind_farm.py::TestWindFarm::test_initialization_1 tests/test_wind_farm.py::TestWindFarm::test_initialization_2 tests/test_wind_farm.py::TestWindFarm::test_initialization_3 tests/test_wind_farm.py::TestWindFarm::test_initialization_4 tests/test_wind_farm.py::TestWindFarm::test_initialization_5 tests/test_wind_farm.py::TestWindFarm::test_initialization_6 tests/test_wind_farm.py::TestWindFarm::test_initialization_7 tests/test_wind_farm.py::TestWindFarm::test_mean_hub_height tests/test_wind_farm.py::TestWindFarm::test_repr tests/test_wind_farm.py::TestWindFarm::test_aggregation_of_power_curve_with_missing_power_curve tests/test_wind_turbine.py::TestWindTurbine::test_warning tests/test_wind_turbine.py::TestWindTurbine::test_get_turbine_data_from_file tests/test_wind_turbine.py::TestWindTurbine::test_wrong_url_load_turbine_data tests/test_wind_turbine.py::TestWindTurbine::test_string_representation_of_wind_turbine tests/test_wind_turbine.py::TestWindTurbine::test_power_curve_is_of_wrong_type tests/test_wind_turbine.py::TestWindTurbine::test_power_coefficient_curve_is_of_wrong_type tests/test_wind_turbine.py::TestWindTurbine::test_to_group_method tests/test_wind_turbine.py::TestWindTurbine::test_wrongly_defined_to_group_method
tests/test_wind_turbine.py::TestWindTurbine::test_get_turbine_types
windpowerlib/wind_turbine_cluster.py doc/temp/windpowerlib.wind_turbine_cluster.WindTurbineCluster.rst doc/getting_started.rst example/turbine_cluster_modelchain_example.py windpowerlib/wind_turbine.py doc/modules.rst doc/conf.py .gitignore example/modelchain_example.py example/turbine_cluster_modelchain_example.ipynb doc/temp/windpowerlib.wind_farm.WindFarm.rst example/modelchain_example.ipynb windpowerlib/wind_farm.py
windpowerlib/wind_turbine_cluster.py doc/temp/windpowerlib.wind_turbine_cluster.WindTurbineCluster.rst doc/getting_started.rst example/turbine_cluster_modelchain_example.py windpowerlib/wind_turbine.py doc/modules.rst doc/conf.py .gitignore example/modelchain_example.py example/turbine_cluster_modelchain_example.ipynb doc/temp/windpowerlib.wind_farm.WindFarm.rst example/modelchain_example.ipynb windpowerlib/wind_farm.py