We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Negative exponents appear positive
**Describe the bug**

Negative exponents do not appear in a profiling report

**To Reproduce**

```python
import numpy as np
import pandas as pd
from pandas_profiling import ProfileReport

data = { 'some_numbers' : (0.0001, 0.00001, 0.00000001, 0.002, 0.0002, 0.00003) * 100}
df = pd.DataFrame(data)
profile = ProfileReport(df, 'No Negative Exponents')
profile.to_file('NoNegativeExponents.html')
```

![no_negative_exponents](https://user-images.githubusercontent.com/1386312/110135651-a7f85b00-7dc6-11eb-8457-67416ade5dea.png)

Minimum should be 1 x 10<sup>-8</sup> rather than 1 x 10<sup>8</sup>. The issue also arises for Mean and Maximum.

**Version information:**

Python 3.7.7 (default, May  6 2020, 11:45:54) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32



**Additional context**

<!--
Add any other context about the problem here.
-->


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/pandas_profiling/report/formatters.py`
Content:
```
1 """Formatters are mappings from object(s) to a string."""
2 from typing import Callable, Dict
3 
4 import numpy as np
5 from jinja2.utils import escape
6 
7 
8 def fmt_color(text: str, color: str) -> str:
9     """Format a string in a certain color (`<span>`).
10 
11     Args:
12       text: The text to format.
13       color: Any valid CSS color.
14 
15     Returns:
16         A `<span>` that contains the colored text.
17     """
18     return f'<span style="color:{color}">{text}</span>'
19 
20 
21 def fmt_class(text: str, cls: str) -> str:
22     """Format a string in a certain class (`<span>`).
23 
24     Args:
25       text: The text to format.
26       cls: The name of the class.
27 
28     Returns:
29         A `<span>` with a class added.
30     """
31     return f'<span class="{cls}">{text}</span>'
32 
33 
34 def fmt_bytesize(num: float, suffix: str = "B") -> str:
35     """Change a number of bytes in a human readable format.
36 
37     Args:
38       num: number to format
39       suffix: (Default value = 'B')
40 
41     Returns:
42       The value formatted in human readable format (e.g. KiB).
43     """
44     for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
45         if abs(num) < 1024.0:
46             return f"{num:3.1f} {unit}{suffix}"
47         num /= 1024.0
48     return f"{num:.1f} Yi{suffix}"
49 
50 
51 def fmt_percent(value: float, edge_cases: bool = True) -> str:
52     """Format a ratio as a percentage.
53 
54     Args:
55         edge_cases: Check for edge cases?
56         value: The ratio.
57 
58     Returns:
59         The percentage with 1 point precision.
60     """
61     if not (1.0 >= value >= 0.0):
62         raise ValueError(f"Value '{value}' should be a ratio between 1 and 0.")
63     if edge_cases and round(value, 3) == 0 and value > 0:
64         return "< 0.1%"
65     if edge_cases and round(value, 3) == 1 and value < 1:
66         return "> 99.9%"
67 
68     return f"{value*100:2.1f}%"
69 
70 
71 def fmt_timespan(num_seconds, detailed=False, max_units=3):
72     # From the `humanfriendly` module (without additional dependency)
73     # https://github.com/xolox/python-humanfriendly/
74     # Author: Peter Odding <peter@peterodding.com>
75     # URL: https://humanfriendly.readthedocs.io
76 
77     import decimal
78     import math
79     import numbers
80     import re
81     from datetime import datetime, timedelta
82 
83     time_units = (
84         dict(
85             divider=1e-9,
86             singular="nanosecond",
87             plural="nanoseconds",
88             abbreviations=["ns"],
89         ),
90         dict(
91             divider=1e-6,
92             singular="microsecond",
93             plural="microseconds",
94             abbreviations=["us"],
95         ),
96         dict(
97             divider=1e-3,
98             singular="millisecond",
99             plural="milliseconds",
100             abbreviations=["ms"],
101         ),
102         dict(
103             divider=1,
104             singular="second",
105             plural="seconds",
106             abbreviations=["s", "sec", "secs"],
107         ),
108         dict(
109             divider=60,
110             singular="minute",
111             plural="minutes",
112             abbreviations=["m", "min", "mins"],
113         ),
114         dict(divider=60 * 60, singular="hour", plural="hours", abbreviations=["h"]),
115         dict(divider=60 * 60 * 24, singular="day", plural="days", abbreviations=["d"]),
116         dict(
117             divider=60 * 60 * 24 * 7,
118             singular="week",
119             plural="weeks",
120             abbreviations=["w"],
121         ),
122         dict(
123             divider=60 * 60 * 24 * 7 * 52,
124             singular="year",
125             plural="years",
126             abbreviations=["y"],
127         ),
128     )
129 
130     def round_number(count, keep_width=False):
131         text = "%.2f" % float(count)
132         if not keep_width:
133             text = re.sub("0+$", "", text)
134             text = re.sub(r"\.$", "", text)
135         return text
136 
137     def coerce_seconds(value):
138         if isinstance(value, timedelta):
139             return value.total_seconds()
140         if not isinstance(value, numbers.Number):
141             msg = "Failed to coerce value to number of seconds! (%r)"
142             raise ValueError(format(msg, value))
143         return value
144 
145     def concatenate(items):
146         items = list(items)
147         if len(items) > 1:
148             return ", ".join(items[:-1]) + " and " + items[-1]
149         elif items:
150             return items[0]
151         else:
152             return ""
153 
154     def pluralize(count, singular, plural=None):
155         if not plural:
156             plural = singular + "s"
157         return "{} {}".format(
158             count, singular if math.floor(float(count)) == 1 else plural
159         )
160 
161     num_seconds = coerce_seconds(num_seconds)
162     if num_seconds < 60 and not detailed:
163         # Fast path.
164         return pluralize(round_number(num_seconds), "second")
165     else:
166         # Slow path.
167         result = []
168         num_seconds = decimal.Decimal(str(num_seconds))
169         relevant_units = list(reversed(time_units[0 if detailed else 3 :]))
170         for unit in relevant_units:
171             # Extract the unit count from the remaining time.
172             divider = decimal.Decimal(str(unit["divider"]))
173             count = num_seconds / divider
174             num_seconds %= divider
175             # Round the unit count appropriately.
176             if unit != relevant_units[-1]:
177                 # Integer rounding for all but the smallest unit.
178                 count = int(count)
179             else:
180                 # Floating point rounding for the smallest unit.
181                 count = round_number(count)
182             # Only include relevant units in the result.
183             if count not in (0, "0"):
184                 result.append(pluralize(count, unit["singular"], unit["plural"]))
185         if len(result) == 1:
186             # A single count/unit combination.
187             return result[0]
188         else:
189             if not detailed:
190                 # Remove `insignificant' data from the formatted timespan.
191                 result = result[:max_units]
192             # Format the timespan in a readable way.
193             return concatenate(result)
194 
195 
196 def fmt_numeric(value: float, precision=10) -> str:
197     """Format any numeric value.
198 
199     Args:
200         value: The numeric value to format.
201         precision: The numeric precision
202 
203     Returns:
204         The numeric value with the given precision.
205     """
206     fmtted = f"{{:.{precision}g}}".format(value)
207     for v in ["e+", "e-"]:
208         if v in fmtted:
209             fmtted = fmtted.replace(v, " × 10<sup>") + "</sup>"
210             fmtted = fmtted.replace("<sup>0", "<sup>")
211 
212     return fmtted
213 
214 
215 def fmt_number(value: int) -> str:
216     """Format any numeric value.
217 
218     Args:
219         value: The numeric value to format.
220 
221     Returns:
222         The numeric value with the given precision.
223     """
224     return f"{value:n}"
225 
226 
227 def fmt_array(value: np.ndarray, threshold=np.nan) -> str:
228     """Format numpy arrays.
229 
230     Args:
231         value: Array to format.
232         threshold: Threshold at which to show ellipsis
233 
234     Returns:
235         The string representation of the numpy array.
236     """
237     with np.printoptions(threshold=3, edgeitems=threshold):
238         return_value = str(value)
239 
240     return return_value
241 
242 
243 def fmt(value) -> str:
244     """Format any value.
245 
246     Args:
247         value: The value to format.
248 
249     Returns:
250         The numeric formatting if the value is float or int, the string formatting otherwise.
251     """
252     if type(value) in [float, int]:
253         return fmt_numeric(value)
254     else:
255         return str(escape(value))
256 
257 
258 def help(title, url=None) -> str:
259     """Creat help badge
260 
261     Args:
262         title: help text
263         url: url to open in new tab (optional)
264 
265     Returns:
266         HTML formatted help badge
267     """
268     if url is not None:
269         return f'<a title="{title}" href="{url}" target="_blank"><span class="badge pull-right" style="color:#fff;background-color:#337ab7;" title="{title}">?</span></a>'
270     else:
271         return f'<span class="badge pull-right" style="color:#fff;background-color:#337ab7;" title="{title}">?</span>'
272 
273 
274 def get_fmt_mapping() -> Dict[str, Callable]:
275     """Get a mapping from formatter name to the function
276 
277     Returns: formatter mapping
278     """
279     return {
280         "fmt_percent": fmt_percent,
281         "fmt_bytesize": fmt_bytesize,
282         "fmt_timespan": fmt_timespan,
283         "fmt_numeric": fmt_numeric,
284         "fmt_number": fmt_number,
285         "fmt_array": fmt_array,
286         "fmt": fmt,
287         "raw": lambda x: x,
288     }
289
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/pandas_profiling/report/formatters.py b/src/pandas_profiling/report/formatters.py
--- a/src/pandas_profiling/report/formatters.py
+++ b/src/pandas_profiling/report/formatters.py
@@ -206,8 +206,10 @@
     fmtted = f"{{:.{precision}g}}".format(value)
     for v in ["e+", "e-"]:
         if v in fmtted:
+            sign = "-" if v in "e-" else ""
             fmtted = fmtted.replace(v, " × 10<sup>") + "</sup>"
             fmtted = fmtted.replace("<sup>0", "<sup>")
+            fmtted = fmtted.replace("<sup>", f"<sup>{sign}")
 
     return fmtted

{"golden_diff": "diff --git a/src/pandas_profiling/report/formatters.py b/src/pandas_profiling/report/formatters.py\n--- a/src/pandas_profiling/report/formatters.py\n+++ b/src/pandas_profiling/report/formatters.py\n@@ -206,8 +206,10 @@\n     fmtted = f\"{{:.{precision}g}}\".format(value)\n     for v in [\"e+\", \"e-\"]:\n         if v in fmtted:\n+            sign = \"-\" if v in \"e-\" else \"\"\n             fmtted = fmtted.replace(v, \" \u00d7 10<sup>\") + \"</sup>\"\n             fmtted = fmtted.replace(\"<sup>0\", \"<sup>\")\n+            fmtted = fmtted.replace(\"<sup>\", f\"<sup>{sign}\")\n \n     return fmtted\n", "issue": "Negative exponents appear positive\n**Describe the bug**\r\n\r\nNegative exponents do not appear in a profiling report\r\n\r\n**To Reproduce**\r\n\r\n```python\r\nimport numpy as np\r\nimport pandas as pd\r\nfrom pandas_profiling import ProfileReport\r\n\r\ndata = { 'some_numbers' : (0.0001, 0.00001, 0.00000001, 0.002, 0.0002, 0.00003) * 100}\r\ndf = pd.DataFrame(data)\r\nprofile = ProfileReport(df, 'No Negative Exponents')\r\nprofile.to_file('NoNegativeExponents.html')\r\n```\r\n\r\n![no_negative_exponents](https://user-images.githubusercontent.com/1386312/110135651-a7f85b00-7dc6-11eb-8457-67416ade5dea.png)\r\n\r\nMinimum should be 1 x 10<sup>-8</sup> rather than 1 x 10<sup>8</sup>. The issue also arises for Mean and Maximum.\r\n\r\n**Version information:**\r\n\r\nPython 3.7.7 (default, May  6 2020, 11:45:54) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32\r\n\r\n\r\n\r\n**Additional context**\r\n\r\n<!--\r\nAdd any other context about the problem here.\r\n-->\r\n\n", "before_files": [{"content": "\"\"\"Formatters are mappings from object(s) to a string.\"\"\"\nfrom typing import Callable, Dict\n\nimport numpy as np\nfrom jinja2.utils import escape\n\n\ndef fmt_color(text: str, color: str) -> str:\n    \"\"\"Format a string in a certain color (`<span>`).\n\n    Args:\n      text: The text to format.\n      color: Any valid CSS color.\n\n    Returns:\n        A `<span>` that contains the colored text.\n    \"\"\"\n    return f'<span style=\"color:{color}\">{text}</span>'\n\n\ndef fmt_class(text: str, cls: str) -> str:\n    \"\"\"Format a string in a certain class (`<span>`).\n\n    Args:\n      text: The text to format.\n      cls: The name of the class.\n\n    Returns:\n        A `<span>` with a class added.\n    \"\"\"\n    return f'<span class=\"{cls}\">{text}</span>'\n\n\ndef fmt_bytesize(num: float, suffix: str = \"B\") -> str:\n    \"\"\"Change a number of bytes in a human readable format.\n\n    Args:\n      num: number to format\n      suffix: (Default value = 'B')\n\n    Returns:\n      The value formatted in human readable format (e.g. KiB).\n    \"\"\"\n    for unit in [\"\", \"Ki\", \"Mi\", \"Gi\", \"Ti\", \"Pi\", \"Ei\", \"Zi\"]:\n        if abs(num) < 1024.0:\n            return f\"{num:3.1f} {unit}{suffix}\"\n        num /= 1024.0\n    return f\"{num:.1f} Yi{suffix}\"\n\n\ndef fmt_percent(value: float, edge_cases: bool = True) -> str:\n    \"\"\"Format a ratio as a percentage.\n\n    Args:\n        edge_cases: Check for edge cases?\n        value: The ratio.\n\n    Returns:\n        The percentage with 1 point precision.\n    \"\"\"\n    if not (1.0 >= value >= 0.0):\n        raise ValueError(f\"Value '{value}' should be a ratio between 1 and 0.\")\n    if edge_cases and round(value, 3) == 0 and value > 0:\n        return \"< 0.1%\"\n    if edge_cases and round(value, 3) == 1 and value < 1:\n        return \"> 99.9%\"\n\n    return f\"{value*100:2.1f}%\"\n\n\ndef fmt_timespan(num_seconds, detailed=False, max_units=3):\n    # From the `humanfriendly` module (without additional dependency)\n    # https://github.com/xolox/python-humanfriendly/\n    # Author: Peter Odding <peter@peterodding.com>\n    # URL: https://humanfriendly.readthedocs.io\n\n    import decimal\n    import math\n    import numbers\n    import re\n    from datetime import datetime, timedelta\n\n    time_units = (\n        dict(\n            divider=1e-9,\n            singular=\"nanosecond\",\n            plural=\"nanoseconds\",\n            abbreviations=[\"ns\"],\n        ),\n        dict(\n            divider=1e-6,\n            singular=\"microsecond\",\n            plural=\"microseconds\",\n            abbreviations=[\"us\"],\n        ),\n        dict(\n            divider=1e-3,\n            singular=\"millisecond\",\n            plural=\"milliseconds\",\n            abbreviations=[\"ms\"],\n        ),\n        dict(\n            divider=1,\n            singular=\"second\",\n            plural=\"seconds\",\n            abbreviations=[\"s\", \"sec\", \"secs\"],\n        ),\n        dict(\n            divider=60,\n            singular=\"minute\",\n            plural=\"minutes\",\n            abbreviations=[\"m\", \"min\", \"mins\"],\n        ),\n        dict(divider=60 * 60, singular=\"hour\", plural=\"hours\", abbreviations=[\"h\"]),\n        dict(divider=60 * 60 * 24, singular=\"day\", plural=\"days\", abbreviations=[\"d\"]),\n        dict(\n            divider=60 * 60 * 24 * 7,\n            singular=\"week\",\n            plural=\"weeks\",\n            abbreviations=[\"w\"],\n        ),\n        dict(\n            divider=60 * 60 * 24 * 7 * 52,\n            singular=\"year\",\n            plural=\"years\",\n            abbreviations=[\"y\"],\n        ),\n    )\n\n    def round_number(count, keep_width=False):\n        text = \"%.2f\" % float(count)\n        if not keep_width:\n            text = re.sub(\"0+$\", \"\", text)\n            text = re.sub(r\"\\.$\", \"\", text)\n        return text\n\n    def coerce_seconds(value):\n        if isinstance(value, timedelta):\n            return value.total_seconds()\n        if not isinstance(value, numbers.Number):\n            msg = \"Failed to coerce value to number of seconds! (%r)\"\n            raise ValueError(format(msg, value))\n        return value\n\n    def concatenate(items):\n        items = list(items)\n        if len(items) > 1:\n            return \", \".join(items[:-1]) + \" and \" + items[-1]\n        elif items:\n            return items[0]\n        else:\n            return \"\"\n\n    def pluralize(count, singular, plural=None):\n        if not plural:\n            plural = singular + \"s\"\n        return \"{} {}\".format(\n            count, singular if math.floor(float(count)) == 1 else plural\n        )\n\n    num_seconds = coerce_seconds(num_seconds)\n    if num_seconds < 60 and not detailed:\n        # Fast path.\n        return pluralize(round_number(num_seconds), \"second\")\n    else:\n        # Slow path.\n        result = []\n        num_seconds = decimal.Decimal(str(num_seconds))\n        relevant_units = list(reversed(time_units[0 if detailed else 3 :]))\n        for unit in relevant_units:\n            # Extract the unit count from the remaining time.\n            divider = decimal.Decimal(str(unit[\"divider\"]))\n            count = num_seconds / divider\n            num_seconds %= divider\n            # Round the unit count appropriately.\n            if unit != relevant_units[-1]:\n                # Integer rounding for all but the smallest unit.\n                count = int(count)\n            else:\n                # Floating point rounding for the smallest unit.\n                count = round_number(count)\n            # Only include relevant units in the result.\n            if count not in (0, \"0\"):\n                result.append(pluralize(count, unit[\"singular\"], unit[\"plural\"]))\n        if len(result) == 1:\n            # A single count/unit combination.\n            return result[0]\n        else:\n            if not detailed:\n                # Remove `insignificant' data from the formatted timespan.\n                result = result[:max_units]\n            # Format the timespan in a readable way.\n            return concatenate(result)\n\n\ndef fmt_numeric(value: float, precision=10) -> str:\n    \"\"\"Format any numeric value.\n\n    Args:\n        value: The numeric value to format.\n        precision: The numeric precision\n\n    Returns:\n        The numeric value with the given precision.\n    \"\"\"\n    fmtted = f\"{{:.{precision}g}}\".format(value)\n    for v in [\"e+\", \"e-\"]:\n        if v in fmtted:\n            fmtted = fmtted.replace(v, \" \u00d7 10<sup>\") + \"</sup>\"\n            fmtted = fmtted.replace(\"<sup>0\", \"<sup>\")\n\n    return fmtted\n\n\ndef fmt_number(value: int) -> str:\n    \"\"\"Format any numeric value.\n\n    Args:\n        value: The numeric value to format.\n\n    Returns:\n        The numeric value with the given precision.\n    \"\"\"\n    return f\"{value:n}\"\n\n\ndef fmt_array(value: np.ndarray, threshold=np.nan) -> str:\n    \"\"\"Format numpy arrays.\n\n    Args:\n        value: Array to format.\n        threshold: Threshold at which to show ellipsis\n\n    Returns:\n        The string representation of the numpy array.\n    \"\"\"\n    with np.printoptions(threshold=3, edgeitems=threshold):\n        return_value = str(value)\n\n    return return_value\n\n\ndef fmt(value) -> str:\n    \"\"\"Format any value.\n\n    Args:\n        value: The value to format.\n\n    Returns:\n        The numeric formatting if the value is float or int, the string formatting otherwise.\n    \"\"\"\n    if type(value) in [float, int]:\n        return fmt_numeric(value)\n    else:\n        return str(escape(value))\n\n\ndef help(title, url=None) -> str:\n    \"\"\"Creat help badge\n\n    Args:\n        title: help text\n        url: url to open in new tab (optional)\n\n    Returns:\n        HTML formatted help badge\n    \"\"\"\n    if url is not None:\n        return f'<a title=\"{title}\" href=\"{url}\" target=\"_blank\"><span class=\"badge pull-right\" style=\"color:#fff;background-color:#337ab7;\" title=\"{title}\">?</span></a>'\n    else:\n        return f'<span class=\"badge pull-right\" style=\"color:#fff;background-color:#337ab7;\" title=\"{title}\">?</span>'\n\n\ndef get_fmt_mapping() -> Dict[str, Callable]:\n    \"\"\"Get a mapping from formatter name to the function\n\n    Returns: formatter mapping\n    \"\"\"\n    return {\n        \"fmt_percent\": fmt_percent,\n        \"fmt_bytesize\": fmt_bytesize,\n        \"fmt_timespan\": fmt_timespan,\n        \"fmt_numeric\": fmt_numeric,\n        \"fmt_number\": fmt_number,\n        \"fmt_array\": fmt_array,\n        \"fmt\": fmt,\n        \"raw\": lambda x: x,\n    }\n", "path": "src/pandas_profiling/report/formatters.py"}], "after_files": [{"content": "\"\"\"Formatters are mappings from object(s) to a string.\"\"\"\nfrom typing import Callable, Dict\n\nimport numpy as np\nfrom jinja2.utils import escape\n\n\ndef fmt_color(text: str, color: str) -> str:\n    \"\"\"Format a string in a certain color (`<span>`).\n\n    Args:\n      text: The text to format.\n      color: Any valid CSS color.\n\n    Returns:\n        A `<span>` that contains the colored text.\n    \"\"\"\n    return f'<span style=\"color:{color}\">{text}</span>'\n\n\ndef fmt_class(text: str, cls: str) -> str:\n    \"\"\"Format a string in a certain class (`<span>`).\n\n    Args:\n      text: The text to format.\n      cls: The name of the class.\n\n    Returns:\n        A `<span>` with a class added.\n    \"\"\"\n    return f'<span class=\"{cls}\">{text}</span>'\n\n\ndef fmt_bytesize(num: float, suffix: str = \"B\") -> str:\n    \"\"\"Change a number of bytes in a human readable format.\n\n    Args:\n      num: number to format\n      suffix: (Default value = 'B')\n\n    Returns:\n      The value formatted in human readable format (e.g. KiB).\n    \"\"\"\n    for unit in [\"\", \"Ki\", \"Mi\", \"Gi\", \"Ti\", \"Pi\", \"Ei\", \"Zi\"]:\n        if abs(num) < 1024.0:\n            return f\"{num:3.1f} {unit}{suffix}\"\n        num /= 1024.0\n    return f\"{num:.1f} Yi{suffix}\"\n\n\ndef fmt_percent(value: float, edge_cases: bool = True) -> str:\n    \"\"\"Format a ratio as a percentage.\n\n    Args:\n        edge_cases: Check for edge cases?\n        value: The ratio.\n\n    Returns:\n        The percentage with 1 point precision.\n    \"\"\"\n    if not (1.0 >= value >= 0.0):\n        raise ValueError(f\"Value '{value}' should be a ratio between 1 and 0.\")\n    if edge_cases and round(value, 3) == 0 and value > 0:\n        return \"< 0.1%\"\n    if edge_cases and round(value, 3) == 1 and value < 1:\n        return \"> 99.9%\"\n\n    return f\"{value*100:2.1f}%\"\n\n\ndef fmt_timespan(num_seconds, detailed=False, max_units=3):\n    # From the `humanfriendly` module (without additional dependency)\n    # https://github.com/xolox/python-humanfriendly/\n    # Author: Peter Odding <peter@peterodding.com>\n    # URL: https://humanfriendly.readthedocs.io\n\n    import decimal\n    import math\n    import numbers\n    import re\n    from datetime import datetime, timedelta\n\n    time_units = (\n        dict(\n            divider=1e-9,\n            singular=\"nanosecond\",\n            plural=\"nanoseconds\",\n            abbreviations=[\"ns\"],\n        ),\n        dict(\n            divider=1e-6,\n            singular=\"microsecond\",\n            plural=\"microseconds\",\n            abbreviations=[\"us\"],\n        ),\n        dict(\n            divider=1e-3,\n            singular=\"millisecond\",\n            plural=\"milliseconds\",\n            abbreviations=[\"ms\"],\n        ),\n        dict(\n            divider=1,\n            singular=\"second\",\n            plural=\"seconds\",\n            abbreviations=[\"s\", \"sec\", \"secs\"],\n        ),\n        dict(\n            divider=60,\n            singular=\"minute\",\n            plural=\"minutes\",\n            abbreviations=[\"m\", \"min\", \"mins\"],\n        ),\n        dict(divider=60 * 60, singular=\"hour\", plural=\"hours\", abbreviations=[\"h\"]),\n        dict(divider=60 * 60 * 24, singular=\"day\", plural=\"days\", abbreviations=[\"d\"]),\n        dict(\n            divider=60 * 60 * 24 * 7,\n            singular=\"week\",\n            plural=\"weeks\",\n            abbreviations=[\"w\"],\n        ),\n        dict(\n            divider=60 * 60 * 24 * 7 * 52,\n            singular=\"year\",\n            plural=\"years\",\n            abbreviations=[\"y\"],\n        ),\n    )\n\n    def round_number(count, keep_width=False):\n        text = \"%.2f\" % float(count)\n        if not keep_width:\n            text = re.sub(\"0+$\", \"\", text)\n            text = re.sub(r\"\\.$\", \"\", text)\n        return text\n\n    def coerce_seconds(value):\n        if isinstance(value, timedelta):\n            return value.total_seconds()\n        if not isinstance(value, numbers.Number):\n            msg = \"Failed to coerce value to number of seconds! (%r)\"\n            raise ValueError(format(msg, value))\n        return value\n\n    def concatenate(items):\n        items = list(items)\n        if len(items) > 1:\n            return \", \".join(items[:-1]) + \" and \" + items[-1]\n        elif items:\n            return items[0]\n        else:\n            return \"\"\n\n    def pluralize(count, singular, plural=None):\n        if not plural:\n            plural = singular + \"s\"\n        return \"{} {}\".format(\n            count, singular if math.floor(float(count)) == 1 else plural\n        )\n\n    num_seconds = coerce_seconds(num_seconds)\n    if num_seconds < 60 and not detailed:\n        # Fast path.\n        return pluralize(round_number(num_seconds), \"second\")\n    else:\n        # Slow path.\n        result = []\n        num_seconds = decimal.Decimal(str(num_seconds))\n        relevant_units = list(reversed(time_units[0 if detailed else 3 :]))\n        for unit in relevant_units:\n            # Extract the unit count from the remaining time.\n            divider = decimal.Decimal(str(unit[\"divider\"]))\n            count = num_seconds / divider\n            num_seconds %= divider\n            # Round the unit count appropriately.\n            if unit != relevant_units[-1]:\n                # Integer rounding for all but the smallest unit.\n                count = int(count)\n            else:\n                # Floating point rounding for the smallest unit.\n                count = round_number(count)\n            # Only include relevant units in the result.\n            if count not in (0, \"0\"):\n                result.append(pluralize(count, unit[\"singular\"], unit[\"plural\"]))\n        if len(result) == 1:\n            # A single count/unit combination.\n            return result[0]\n        else:\n            if not detailed:\n                # Remove `insignificant' data from the formatted timespan.\n                result = result[:max_units]\n            # Format the timespan in a readable way.\n            return concatenate(result)\n\n\ndef fmt_numeric(value: float, precision=10) -> str:\n    \"\"\"Format any numeric value.\n\n    Args:\n        value: The numeric value to format.\n        precision: The numeric precision\n\n    Returns:\n        The numeric value with the given precision.\n    \"\"\"\n    fmtted = f\"{{:.{precision}g}}\".format(value)\n    for v in [\"e+\", \"e-\"]:\n        if v in fmtted:\n            sign = \"-\" if v in \"e-\" else \"\"\n            fmtted = fmtted.replace(v, \" \u00d7 10<sup>\") + \"</sup>\"\n            fmtted = fmtted.replace(\"<sup>0\", \"<sup>\")\n            fmtted = fmtted.replace(\"<sup>\", f\"<sup>{sign}\")\n\n    return fmtted\n\n\ndef fmt_number(value: int) -> str:\n    \"\"\"Format any numeric value.\n\n    Args:\n        value: The numeric value to format.\n\n    Returns:\n        The numeric value with the given precision.\n    \"\"\"\n    return f\"{value:n}\"\n\n\ndef fmt_array(value: np.ndarray, threshold=np.nan) -> str:\n    \"\"\"Format numpy arrays.\n\n    Args:\n        value: Array to format.\n        threshold: Threshold at which to show ellipsis\n\n    Returns:\n        The string representation of the numpy array.\n    \"\"\"\n    with np.printoptions(threshold=3, edgeitems=threshold):\n        return_value = str(value)\n\n    return return_value\n\n\ndef fmt(value) -> str:\n    \"\"\"Format any value.\n\n    Args:\n        value: The value to format.\n\n    Returns:\n        The numeric formatting if the value is float or int, the string formatting otherwise.\n    \"\"\"\n    if type(value) in [float, int]:\n        return fmt_numeric(value)\n    else:\n        return str(escape(value))\n\n\ndef help(title, url=None) -> str:\n    \"\"\"Creat help badge\n\n    Args:\n        title: help text\n        url: url to open in new tab (optional)\n\n    Returns:\n        HTML formatted help badge\n    \"\"\"\n    if url is not None:\n        return f'<a title=\"{title}\" href=\"{url}\" target=\"_blank\"><span class=\"badge pull-right\" style=\"color:#fff;background-color:#337ab7;\" title=\"{title}\">?</span></a>'\n    else:\n        return f'<span class=\"badge pull-right\" style=\"color:#fff;background-color:#337ab7;\" title=\"{title}\">?</span>'\n\n\ndef get_fmt_mapping() -> Dict[str, Callable]:\n    \"\"\"Get a mapping from formatter name to the function\n\n    Returns: formatter mapping\n    \"\"\"\n    return {\n        \"fmt_percent\": fmt_percent,\n        \"fmt_bytesize\": fmt_bytesize,\n        \"fmt_timespan\": fmt_timespan,\n        \"fmt_numeric\": fmt_numeric,\n        \"fmt_number\": fmt_number,\n        \"fmt_array\": fmt_array,\n        \"fmt\": fmt,\n        \"raw\": lambda x: x,\n    }\n", "path": "src/pandas_profiling/report/formatters.py"}]}