nodes: 0, module; 1, function_definition; 2, function_name:wide_to_long; 3, parameters; 4, block; 5, identifier:df; 6, identifier:stubnames; 7, identifier:i; 8, identifier:j; 9, default_parameter; 10, default_parameter; 11, expression_statement; 12, function_definition; 13, function_definition; 14, if_statement; 15, if_statement; 16, if_statement; 17, if_statement; 18, expression_statement; 19, expression_statement; 20, expression_statement; 21, expression_statement; 22, expression_statement; 23, if_statement; 24, expression_statement; 25, return_statement; 26, identifier:sep; 27, string:""; 28, identifier:suffix; 29, string; 30, comment:r"""
      Wide panel to long format. Less flexible but more user-friendly than melt.
  
      With stubnames ['A', 'B'], this function expects to find one or more
      group of columns with format
      A-suffix1, A-suffix2,..., B-suffix1, B-suffix2,...
      You specify what you want to call this suffix in the resulting long format
      with `j` (for example `j='year'`)
  
      Each row of these wide variables are assumed to be uniquely identified by
      `i` (can be a single column name or a list of column names)
  
      All remaining variables in the data frame are left intact.
  
      Parameters
      ----------
      df : DataFrame
          The wide-format DataFrame
      stubnames : str or list-like
          The stub name(s). The wide format variables are assumed to
          start with the stub names.
      i : str or list-like
          Column(s) to use as id variable(s)
      j : str
          The name of the sub-observation variable. What you wish to name your
          suffix in the long format.
      sep : str, default ""
          A character indicating the separation of the variable names
          in the wide format, to be stripped from the names in the long format.
          For example, if your column names are A-suffix1, A-suffix2, you
          can strip the hyphen by specifying `sep='-'`
  
          .. versionadded:: 0.20.0
  
      suffix : str, default '\\d+'
          A regular expression capturing the wanted suffixes. '\\d+' captures
          numeric suffixes. Suffixes with no numbers could be specified with the
          negated character class '\\D+'. You can also further disambiguate
          suffixes, for example, if your wide variables are of the form
          A-one, B-two,.., and you have an unrelated column A-rating, you can
          ignore the last one by specifying `suffix='(!?one|two)'`
  
          .. versionadded:: 0.20.0
  
          .. versionchanged:: 0.23.0
              When all suffixes are numeric, they are cast to int64/float64.
  
      Returns
      -------
      DataFrame
          A DataFrame that contains each stub name as a variable, with new index
          (i, j).
  
      Notes
      -----
      All extra variables are left untouched. This simply uses
      `pandas.melt` under the hood, but is hard-coded to "do the right thing"
      in a typical case.
  
      Examples
      --------
      >>> np.random.seed(123)
      >>> df = pd.DataFrame({"A1970" : {0 : "a", 1 : "b", 2 : "c"},
      ...                    "A1980" : {0 : "d", 1 : "e", 2 : "f"},
      ...                    "B1970" : {0 : 2.5, 1 : 1.2, 2 : .7},
      ...                    "B1980" : {0 : 3.2, 1 : 1.3, 2 : .1},
      ...                    "X"     : dict(zip(range(3), np.random.randn(3)))
      ...                   })
      >>> df["id"] = df.index
      >>> df
        A1970 A1980  B1970  B1980         X  id
      0     a     d    2.5    3.2 -1.085631   0
      1     b     e    1.2    1.3  0.997345   1
      2     c     f    0.7    0.1  0.282978   2
      >>> pd.wide_to_long(df, ["A", "B"], i="id", j="year")
      ... # doctest: +NORMALIZE_WHITESPACE
                      X  A    B
      id year
      0  1970 -1.085631  a  2.5
      1  1970  0.997345  b  1.2
      2  1970  0.282978  c  0.7
      0  1980 -1.085631  d  3.2
      1  1980  0.997345  e  1.3
      2  1980  0.282978  f  0.1
  
      With multiple id columns
  
      >>> df = pd.DataFrame({
      ...     'famid': [1, 1, 1, 2, 2, 2, 3, 3, 3],
      ...     'birth': [1, 2, 3, 1, 2, 3, 1, 2, 3],
      ...     'ht1': [2.8, 2.9, 2.2, 2, 1.8, 1.9, 2.2, 2.3, 2.1],
      ...     'ht2': [3.4, 3.8, 2.9, 3.2, 2.8, 2.4, 3.3, 3.4, 2.9]
      ... })
      >>> df
         birth  famid  ht1  ht2
      0      1      1  2.8  3.4
      1      2      1  2.9  3.8
      2      3      1  2.2  2.9
      3      1      2  2.0  3.2
      4      2      2  1.8  2.8
      5      3      2  1.9  2.4
      6      1      3  2.2  3.3
      7      2      3  2.3  3.4
      8      3      3  2.1  2.9
      >>> l = pd.wide_to_long(df, stubnames='ht', i=['famid', 'birth'], j='age')
      >>> l
      ... # doctest: +NORMALIZE_WHITESPACE
                        ht
      famid birth age
      1     1     1    2.8
                  2    3.4
            2     1    2.9
                  2    3.8
            3     1    2.2
                  2    2.9
      2     1     1    2.0
                  2    3.2
            2     1    1.8
                  2    2.8
            3     1    1.9
                  2    2.4
      3     1     1    2.2
                  2    3.3
            2     1    2.3
                  2    3.4
            3     1    2.1
                  2    2.9
  
      Going from long back to wide just takes some creative use of `unstack`
  
      >>> w = l.unstack()
      >>> w.columns = w.columns.map('{0[0]}{0[1]}'.format)
      >>> w.reset_index()
         famid  birth  ht1  ht2
      0      1      1  2.8  3.4
      1      1      2  2.9  3.8
      2      1      3  2.2  2.9
      3      2      1  2.0  3.2
      4      2      2  1.8  2.8
      5      2      3  1.9  2.4
      6      3      1  2.2  3.3
      7      3      2  2.3  3.4
      8      3      3  2.1  2.9
  
      Less wieldy column names are also handled
  
      >>> np.random.seed(0)
      >>> df = pd.DataFrame({'A(quarterly)-2010': np.random.rand(3),
      ...                    'A(quarterly)-2011': np.random.rand(3),
      ...                    'B(quarterly)-2010': np.random.rand(3),
      ...                    'B(quarterly)-2011': np.random.rand(3),
      ...                    'X' : np.random.randint(3, size=3)})
      >>> df['id'] = df.index
      >>> df # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS
         A(quarterly)-2010  A(quarterly)-2011  B(quarterly)-2010  ...
      0           0.548814           0.544883           0.437587  ...
      1           0.715189           0.423655           0.891773  ...
      2           0.602763           0.645894           0.963663  ...
         X  id
      0  0   0
      1  1   1
      2  1   2
  
      >>> pd.wide_to_long(df, ['A(quarterly)', 'B(quarterly)'], i='id',
      ...                 j='year', sep='-')
      ... # doctest: +NORMALIZE_WHITESPACE
               X  A(quarterly)  B(quarterly)
      id year
      0  2010  0      0.548814     0.437587
      1  2010  1      0.715189     0.891773
      2  2010  1      0.602763     0.963663
      0  2011  0      0.544883     0.383442
      1  2011  1      0.423655     0.791725
      2  2011  1      0.645894     0.528895
  
      If we have many columns, we could also use a regex to find our
      stubnames and pass that list on to wide_to_long
  
      >>> stubnames = sorted(
      ...     set([match[0] for match in df.columns.str.findall(
      ...         r'[A-B]\(.*\)').values if match != [] ])
      ... )
      >>> list(stubnames)
      ['A(quarterly)', 'B(quarterly)']
  
      All of the above examples have integers as suffixes. It is possible to
      have non-integers as suffixes.
  
      >>> df = pd.DataFrame({
      ...     'famid': [1, 1, 1, 2, 2, 2, 3, 3, 3],
      ...     'birth': [1, 2, 3, 1, 2, 3, 1, 2, 3],
      ...     'ht_one': [2.8, 2.9, 2.2, 2, 1.8, 1.9, 2.2, 2.3, 2.1],
      ...     'ht_two': [3.4, 3.8, 2.9, 3.2, 2.8, 2.4, 3.3, 3.4, 2.9]
      ... })
      >>> df
         birth  famid  ht_one  ht_two
      0      1      1     2.8     3.4
      1      2      1     2.9     3.8
      2      3      1     2.2     2.9
      3      1      2     2.0     3.2
      4      2      2     1.8     2.8
      5      3      2     1.9     2.4
      6      1      3     2.2     3.3
      7      2      3     2.3     3.4
      8      3      3     2.1     2.9
  
      >>> l = pd.wide_to_long(df, stubnames='ht', i=['famid', 'birth'], j='age',
                              sep='_', suffix='\w')
      >>> l
      ... # doctest: +NORMALIZE_WHITESPACE
                        ht
      famid birth age
      1     1     one  2.8
                  two  3.4
            2     one  2.9
                  two  3.8
            3     one  2.2
                  two  2.9
      2     1     one  2.0
                  two  3.2
            2     one  1.8
                  two  2.8
            3     one  1.9
                  two  2.4
      3     1     one  2.2
                  two  3.3
            2     one  2.3
                  two  3.4
            3     one  2.1
                  two  2.9
      """; 31, function_name:get_var_names; 32, parameters; 33, block; 34, function_name:melt_stub; 35, parameters; 36, block; 37, not_operator; 38, block; 39, else_clause; 40, call; 41, block; 42, not_operator; 43, block; 44, else_clause; 45, call; 46, block; 47, assignment; 48, assignment; 49, assignment; 50, assignment; 51, assignment; 52, comparison_operator:len(i) == 1; 53, block; 54, assignment; 55, identifier:new; 56, string_content:\d+; 57, identifier:df; 58, identifier:stub; 59, identifier:sep; 60, identifier:suffix; 61, expression_statement; 62, expression_statement; 63, return_statement; 64, identifier:df; 65, identifier:stub; 66, identifier:i; 67, identifier:j; 68, identifier:value_vars; 69, identifier:sep; 70, expression_statement; 71, expression_statement; 72, expression_statement; 73, comment:# GH17627 Cast numerics suffixes to int/float; 74, expression_statement; 75, return_statement; 76, call; 77, expression_statement; 78, block; 79, identifier:any; 80, generator_expression; 81, raise_statement; 82, call; 83, expression_statement; 84, block; 85, attribute; 86, argument_list; 87, raise_statement; 88, identifier:value_vars; 89, list_comprehension; 90, identifier:value_vars_flattened; 91, list_comprehension; 92, identifier:id_vars; 93, call; 94, identifier:melted; 95, list_comprehension; 96, identifier:melted; 97, call; 98, call; 99, integer:1; 100, expression_statement; 101, return_statement; 102, identifier:new; 103, call; 104, assignment; 105, assignment; 106, list_comprehension; 107, assignment; 108, assignment; 109, assignment; 110, assignment; 111, call; 112, identifier:is_list_like; 113, argument_list; 114, assignment; 115, expression_statement; 116, comparison_operator:col in stubnames; 117, for_in_clause; 118, call; 119, identifier:is_list_like; 120, argument_list; 121, assignment; 122, expression_statement; 123, call; 124, identifier:any; 125, call; 126, call; 127, for_in_clause; 128, identifier:e; 129, for_in_clause; 130, for_in_clause; 131, identifier:list; 132, argument_list; 133, call; 134, for_in_clause; 135, attribute; 136, argument_list; 137, identifier:len; 138, argument_list; 139, assignment; 140, identifier:new; 141, attribute; 142, argument_list; 143, identifier:regex; 144, call; 145, identifier:pattern; 146, call; 147, identifier:col; 148, for_in_clause; 149, if_clause; 150, identifier:newdf; 151, call; 152, subscript; 153, call; 154, subscript; 155, call; 156, subscript; 157, call; 158, attribute; 159, argument_list; 160, identifier:stubnames; 161, identifier:stubnames; 162, list; 163, assignment; 164, identifier:col; 165, identifier:stubnames; 166, identifier:col; 167, attribute; 168, identifier:ValueError; 169, argument_list; 170, identifier:i; 171, identifier:i; 172, list; 173, assignment; 174, attribute; 175, argument_list; 176, identifier:ValueError; 177, argument_list; 178, identifier:get_var_names; 179, argument_list; 180, identifier:stub; 181, identifier:stubnames; 182, identifier:sublist; 183, identifier:value_vars; 184, identifier:e; 185, identifier:sublist; 186, call; 187, identifier:melt_stub; 188, argument_list; 189, pattern_list; 190, call; 191, subscript; 192, identifier:join; 193, subscript; 194, keyword_argument; 195, identifier:i; 196, identifier:new; 197, call; 198, call; 199, identifier:set_index; 200, binary_operator:i + [j]; 201, attribute; 202, argument_list; 203, attribute; 204, argument_list; 205, identifier:col; 206, attribute; 207, call; 208, identifier:melt; 209, argument_list; 210, identifier:newdf; 211, identifier:j; 212, identifier:Categorical; 213, argument_list; 214, identifier:newdf; 215, identifier:j; 216, attribute; 217, argument_list; 218, identifier:newdf; 219, identifier:j; 220, identifier:to_numeric; 221, argument_list; 222, identifier:newdf; 223, identifier:set_index; 224, binary_operator:i + [j]; 225, identifier:stubnames; 226, identifier:stubnames; 227, call; 228, identifier:df; 229, identifier:columns; 230, string:"stubname can't be identical to a column name"; 231, identifier:i; 232, identifier:i; 233, call; 234, subscript; 235, identifier:duplicated; 236, string:"the id variables need to uniquely identify each row"; 237, identifier:df; 238, identifier:stub; 239, identifier:sep; 240, identifier:suffix; 241, attribute; 242, argument_list; 243, identifier:df; 244, identifier:s; 245, identifier:i; 246, identifier:j; 247, identifier:v; 248, identifier:sep; 249, identifier:s; 250, identifier:v; 251, identifier:zip; 252, argument_list; 253, identifier:melted; 254, integer:0; 255, identifier:melted; 256, slice; 257, identifier:how; 258, string; 259, attribute; 260, argument_list; 261, attribute; 262, argument_list; 263, identifier:i; 264, list; 265, string; 266, identifier:format; 267, keyword_argument; 268, keyword_argument; 269, keyword_argument; 270, identifier:re; 271, identifier:compile; 272, identifier:regex; 273, identifier:df; 274, identifier:columns; 275, attribute; 276, argument_list; 277, identifier:df; 278, keyword_argument; 279, keyword_argument; 280, keyword_argument; 281, keyword_argument; 282, subscript; 283, attribute; 284, identifier:replace; 285, call; 286, string:""; 287, subscript; 288, keyword_argument; 289, identifier:i; 290, list; 291, identifier:list; 292, argument_list; 293, identifier:list; 294, argument_list; 295, identifier:df; 296, identifier:i; 297, call; 298, identifier:difference; 299, identifier:value_vars_flattened; 300, identifier:stubnames; 301, identifier:value_vars; 302, integer:1; 303, string_content:outer; 304, call; 305, identifier:join; 306, identifier:melted; 307, subscript; 308, identifier:merge; 309, call; 310, keyword_argument; 311, identifier:j; 312, string_content:^{stub}{sep}{suffix}$; 313, identifier:stub; 314, call; 315, identifier:sep; 316, call; 317, identifier:suffix; 318, identifier:suffix; 319, identifier:pattern; 320, identifier:match; 321, identifier:col; 322, identifier:id_vars; 323, identifier:i; 324, identifier:value_vars; 325, identifier:value_vars; 326, identifier:value_name; 327, call; 328, identifier:var_name; 329, identifier:j; 330, identifier:newdf; 331, identifier:j; 332, subscript; 333, identifier:str; 334, attribute; 335, argument_list; 336, identifier:newdf; 337, identifier:j; 338, identifier:errors; 339, string; 340, identifier:j; 341, identifier:stubnames; 342, identifier:i; 343, identifier:set; 344, argument_list; 345, attribute; 346, argument_list; 347, identifier:df; 348, identifier:id_vars; 349, attribute; 350, argument_list; 351, identifier:on; 352, identifier:i; 353, attribute; 354, argument_list; 355, attribute; 356, argument_list; 357, attribute; 358, argument_list; 359, identifier:newdf; 360, identifier:j; 361, identifier:re; 362, identifier:escape; 363, binary_operator:stub + sep; 364, string_content:ignore; 365, call; 366, subscript; 367, identifier:set_index; 368, identifier:i; 369, identifier:melted; 370, identifier:reset_index; 371, identifier:re; 372, identifier:escape; 373, identifier:stub; 374, identifier:re; 375, identifier:escape; 376, identifier:sep; 377, identifier:stub; 378, identifier:rstrip; 379, identifier:sep; 380, identifier:stub; 381, identifier:sep; 382, attribute; 383, argument_list; 384, identifier:df; 385, identifier:id_vars; 386, attribute; 387, identifier:tolist; 388, identifier:df; 389, identifier:columns
code: def wide_to_long(df, stubnames, i, j, sep="", suffix=r'\d+'):
      r"""
      Wide panel to long format. Less flexible but more user-friendly than melt.
  
      With stubnames ['A', 'B'], this function expects to find one or more
      group of columns with format
      A-suffix1, A-suffix2,..., B-suffix1, B-suffix2,...
      You specify what you want to call this suffix in the resulting long format
      with `j` (for example `j='year'`)
  
      Each row of these wide variables are assumed to be uniquely identified by
      `i` (can be a single column name or a list of column names)
  
      All remaining variables in the data frame are left intact.
  
      Parameters
      ----------
      df : DataFrame
          The wide-format DataFrame
      stubnames : str or list-like
          The stub name(s). The wide format variables are assumed to
          start with the stub names.
      i : str or list-like
          Column(s) to use as id variable(s)
      j : str
          The name of the sub-observation variable. What you wish to name your
          suffix in the long format.
      sep : str, default ""
          A character indicating the separation of the variable names
          in the wide format, to be stripped from the names in the long format.
          For example, if your column names are A-suffix1, A-suffix2, you
          can strip the hyphen by specifying `sep='-'`
  
          .. versionadded:: 0.20.0
  
      suffix : str, default '\\d+'
          A regular expression capturing the wanted suffixes. '\\d+' captures
          numeric suffixes. Suffixes with no numbers could be specified with the
          negated character class '\\D+'. You can also further disambiguate
          suffixes, for example, if your wide variables are of the form
          A-one, B-two,.., and you have an unrelated column A-rating, you can
          ignore the last one by specifying `suffix='(!?one|two)'`
  
          .. versionadded:: 0.20.0
  
          .. versionchanged:: 0.23.0
              When all suffixes are numeric, they are cast to int64/float64.
  
      Returns
      -------
      DataFrame
          A DataFrame that contains each stub name as a variable, with new index
          (i, j).
  
      Notes
      -----
      All extra variables are left untouched. This simply uses
      `pandas.melt` under the hood, but is hard-coded to "do the right thing"
      in a typical case.
  
      Examples
      --------
      >>> np.random.seed(123)
      >>> df = pd.DataFrame({"A1970" : {0 : "a", 1 : "b", 2 : "c"},
      ...                    "A1980" : {0 : "d", 1 : "e", 2 : "f"},
      ...                    "B1970" : {0 : 2.5, 1 : 1.2, 2 : .7},
      ...                    "B1980" : {0 : 3.2, 1 : 1.3, 2 : .1},
      ...                    "X"     : dict(zip(range(3), np.random.randn(3)))
      ...                   })
      >>> df["id"] = df.index
      >>> df
        A1970 A1980  B1970  B1980         X  id
      0     a     d    2.5    3.2 -1.085631   0
      1     b     e    1.2    1.3  0.997345   1
      2     c     f    0.7    0.1  0.282978   2
      >>> pd.wide_to_long(df, ["A", "B"], i="id", j="year")
      ... # doctest: +NORMALIZE_WHITESPACE
                      X  A    B
      id year
      0  1970 -1.085631  a  2.5
      1  1970  0.997345  b  1.2
      2  1970  0.282978  c  0.7
      0  1980 -1.085631  d  3.2
      1  1980  0.997345  e  1.3
      2  1980  0.282978  f  0.1
  
      With multiple id columns
  
      >>> df = pd.DataFrame({
      ...     'famid': [1, 1, 1, 2, 2, 2, 3, 3, 3],
      ...     'birth': [1, 2, 3, 1, 2, 3, 1, 2, 3],
      ...     'ht1': [2.8, 2.9, 2.2, 2, 1.8, 1.9, 2.2, 2.3, 2.1],
      ...     'ht2': [3.4, 3.8, 2.9, 3.2, 2.8, 2.4, 3.3, 3.4, 2.9]
      ... })
      >>> df
         birth  famid  ht1  ht2
      0      1      1  2.8  3.4
      1      2      1  2.9  3.8
      2      3      1  2.2  2.9
      3      1      2  2.0  3.2
      4      2      2  1.8  2.8
      5      3      2  1.9  2.4
      6      1      3  2.2  3.3
      7      2      3  2.3  3.4
      8      3      3  2.1  2.9
      >>> l = pd.wide_to_long(df, stubnames='ht', i=['famid', 'birth'], j='age')
      >>> l
      ... # doctest: +NORMALIZE_WHITESPACE
                        ht
      famid birth age
      1     1     1    2.8
                  2    3.4
            2     1    2.9
                  2    3.8
            3     1    2.2
                  2    2.9
      2     1     1    2.0
                  2    3.2
            2     1    1.8
                  2    2.8
            3     1    1.9
                  2    2.4
      3     1     1    2.2
                  2    3.3
            2     1    2.3
                  2    3.4
            3     1    2.1
                  2    2.9
  
      Going from long back to wide just takes some creative use of `unstack`
  
      >>> w = l.unstack()
      >>> w.columns = w.columns.map('{0[0]}{0[1]}'.format)
      >>> w.reset_index()
         famid  birth  ht1  ht2
      0      1      1  2.8  3.4
      1      1      2  2.9  3.8
      2      1      3  2.2  2.9
      3      2      1  2.0  3.2
      4      2      2  1.8  2.8
      5      2      3  1.9  2.4
      6      3      1  2.2  3.3
      7      3      2  2.3  3.4
      8      3      3  2.1  2.9
  
      Less wieldy column names are also handled
  
      >>> np.random.seed(0)
      >>> df = pd.DataFrame({'A(quarterly)-2010': np.random.rand(3),
      ...                    'A(quarterly)-2011': np.random.rand(3),
      ...                    'B(quarterly)-2010': np.random.rand(3),
      ...                    'B(quarterly)-2011': np.random.rand(3),
      ...                    'X' : np.random.randint(3, size=3)})
      >>> df['id'] = df.index
      >>> df # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS
         A(quarterly)-2010  A(quarterly)-2011  B(quarterly)-2010  ...
      0           0.548814           0.544883           0.437587  ...
      1           0.715189           0.423655           0.891773  ...
      2           0.602763           0.645894           0.963663  ...
         X  id
      0  0   0
      1  1   1
      2  1   2
  
      >>> pd.wide_to_long(df, ['A(quarterly)', 'B(quarterly)'], i='id',
      ...                 j='year', sep='-')
      ... # doctest: +NORMALIZE_WHITESPACE
               X  A(quarterly)  B(quarterly)
      id year
      0  2010  0      0.548814     0.437587
      1  2010  1      0.715189     0.891773
      2  2010  1      0.602763     0.963663
      0  2011  0      0.544883     0.383442
      1  2011  1      0.423655     0.791725
      2  2011  1      0.645894     0.528895
  
      If we have many columns, we could also use a regex to find our
      stubnames and pass that list on to wide_to_long
  
      >>> stubnames = sorted(
      ...     set([match[0] for match in df.columns.str.findall(
      ...         r'[A-B]\(.*\)').values if match != [] ])
      ... )
      >>> list(stubnames)
      ['A(quarterly)', 'B(quarterly)']
  
      All of the above examples have integers as suffixes. It is possible to
      have non-integers as suffixes.
  
      >>> df = pd.DataFrame({
      ...     'famid': [1, 1, 1, 2, 2, 2, 3, 3, 3],
      ...     'birth': [1, 2, 3, 1, 2, 3, 1, 2, 3],
      ...     'ht_one': [2.8, 2.9, 2.2, 2, 1.8, 1.9, 2.2, 2.3, 2.1],
      ...     'ht_two': [3.4, 3.8, 2.9, 3.2, 2.8, 2.4, 3.3, 3.4, 2.9]
      ... })
      >>> df
         birth  famid  ht_one  ht_two
      0      1      1     2.8     3.4
      1      2      1     2.9     3.8
      2      3      1     2.2     2.9
      3      1      2     2.0     3.2
      4      2      2     1.8     2.8
      5      3      2     1.9     2.4
      6      1      3     2.2     3.3
      7      2      3     2.3     3.4
      8      3      3     2.1     2.9
  
      >>> l = pd.wide_to_long(df, stubnames='ht', i=['famid', 'birth'], j='age',
                              sep='_', suffix='\w')
      >>> l
      ... # doctest: +NORMALIZE_WHITESPACE
                        ht
      famid birth age
      1     1     one  2.8
                  two  3.4
            2     one  2.9
                  two  3.8
            3     one  2.2
                  two  2.9
      2     1     one  2.0
                  two  3.2
            2     one  1.8
                  two  2.8
            3     one  1.9
                  two  2.4
      3     1     one  2.2
                  two  3.3
            2     one  2.3
                  two  3.4
            3     one  2.1
                  two  2.9
      """
      def get_var_names(df, stub, sep, suffix):
          regex = r'^{stub}{sep}{suffix}$'.format(
              stub=re.escape(stub), sep=re.escape(sep), suffix=suffix)
          pattern = re.compile(regex)
          return [col for col in df.columns if pattern.match(col)]
  
      def melt_stub(df, stub, i, j, value_vars, sep):
          newdf = melt(df, id_vars=i, value_vars=value_vars,
                       value_name=stub.rstrip(sep), var_name=j)
          newdf[j] = Categorical(newdf[j])
          newdf[j] = newdf[j].str.replace(re.escape(stub + sep), "")
  
          # GH17627 Cast numerics suffixes to int/float
          newdf[j] = to_numeric(newdf[j], errors='ignore')
  
          return newdf.set_index(i + [j])
  
      if not is_list_like(stubnames):
          stubnames = [stubnames]
      else:
          stubnames = list(stubnames)
  
      if any(col in stubnames for col in df.columns):
          raise ValueError("stubname can't be identical to a column name")
  
      if not is_list_like(i):
          i = [i]
      else:
          i = list(i)
  
      if df[i].duplicated().any():
          raise ValueError("the id variables need to uniquely identify each row")
  
      value_vars = [get_var_names(df, stub, sep, suffix) for stub in stubnames]
  
      value_vars_flattened = [e for sublist in value_vars for e in sublist]
      id_vars = list(set(df.columns.tolist()).difference(value_vars_flattened))
  
      melted = [melt_stub(df, s, i, j, v, sep)
                for s, v in zip(stubnames, value_vars)]
      melted = melted[0].join(melted[1:], how='outer')
  
      if len(i) == 1:
          new = df[id_vars].set_index(i).join(melted)
          return new
  
      new = df[id_vars].merge(melted.reset_index(), on=i).set_index(i + [j])
  
      return new