分类

链接

2022 年 7 月
 123
45678910
11121314151617
18192021222324
25262728293031

近期文章

热门标签

新人福利,免费薅羊毛

现在位置:    首页 > Python > 正文
共享办公室出租
可在阿里云dataworks直接使用的worldbank data api
Python 暂无评论 阅读(98)

代码下载:

WorldBankAPI.zip

  1. """
  2. wbdata: A wrapper for the World Bank API
  3. """
  4. __version__ ="0.3.0"
  5. """
  6. wbdata.api: Where all the functions go
  7. """
  8. import collections
  9. import datetime
  10. import re
  11. import warnings
  12. import tabulate
  13. try:
  14. import pandas as pd
  15. exceptImportError:
  16.     pd =None
  17. from decorator import decorator
  18. BASE_URL ="https://api.worldbank.org/v2"
  19. COUNTRIES_URL = f"{BASE_URL}/countries"
  20. ILEVEL_URL = f"{BASE_URL}/incomeLevels"
  21. INDICATOR_URL = f"{BASE_URL}/indicators"
  22. LTYPE_URL = f"{BASE_URL}/lendingTypes"
  23. SOURCES_URL = f"{BASE_URL}/sources"
  24. TOPIC_URL = f"{BASE_URL}/topics"
  25. INDIC_ERROR ="Cannot specify more than one of indicator, source, and topic"
  26. classWBSearchResult(list):
  27. """
  28.     A list that prints out a user-friendly table when printed or returned on the
  29.     command line
  30.     Items are expected to be dict-like and have an "id" key and a "name" or
  31.     "value" key
  32.     """
  33. def __repr__(self):
  34. try:
  35. return tabulate.tabulate(
  36. [[o["id"], o["name"]]for o in self],
  37.                 headers=["id","name"],
  38.                 tablefmt="simple",
  39. )
  40. exceptKeyError:
  41. return tabulate.tabulate(
  42. [[o["id"], o["value"]]for o in self],
  43.                 headers=["id","value"],
  44.                 tablefmt="simple",
  45. )
  46. if pd:
  47. classWBSeries(pd.Series):
  48. """
  49.         A pandas Series with a last_updated attribute
  50.         """
  51.         _metadata =["last_updated"]
  52. @property
  53. def _constructor(self):
  54. returnWBSeries
  55. classWBDataFrame(pd.DataFrame):
  56. """
  57.         A pandas DataFrame with a last_updated attribute
  58.         """
  59.         _metadata =["last_updated"]
  60. @property
  61. def _constructor(self):
  62. returnWBDataFrame
  63. @decorator
  64. def uses_pandas(f,*args,**kwargs):
  65. """Raise ValueError if pandas is not loaded"""
  66. ifnot pd:
  67. raiseValueError("Pandas must be installed to be used")
  68. return f(*args,**kwargs)
  69. def parse_value_or_iterable(arg):
  70. """
  71.     If arg is a single value, return it as a string; if an iterable, return a
  72.     ;-joined string of all values
  73.     """
  74. if str(arg)== arg:
  75. return arg
  76. if type(arg)== int:
  77. return str(arg)
  78. return";".join(arg)
  79. def convert_year_to_datetime(yearstr):
  80. """return datetime.datetime object from %Y formatted string"""
  81. return datetime.datetime.strptime(yearstr,"%Y")
  82. def convert_month_to_datetime(monthstr):
  83. """return datetime.datetime object from %YM%m formatted string"""
  84.     split = monthstr.split("M")
  85. return datetime.datetime(int(split[0]), int(split[1]),1)
  86. def convert_quarter_to_datetime(quarterstr):
  87. """
  88.     return datetime.datetime object from %YQ%# formatted string, where # is
  89.     the desired quarter
  90.     """
  91.     split = quarterstr.split("Q")
  92.     quarter = int(split[1])
  93.     month = quarter *3-2
  94. return datetime.datetime(int(split[0]), month,1)
  95. def convert_dates_to_datetime(data):
  96. """
  97.     Return a datetime.datetime object from a date string as provided by the
  98.     World Bank
  99.     """
  100.     first = data[0]["date"]
  101. if isinstance(first, datetime.datetime):
  102. return data
  103. if"M"in first:
  104.         converter = convert_month_to_datetime
  105. elif"Q"in first:
  106.         converter = convert_quarter_to_datetime
  107. else:
  108.         converter = convert_year_to_datetime
  109. for datum in data:
  110.         datum_date = datum["date"]
  111. if"MRV"in datum_date:
  112. continue
  113. if"-"in datum_date:
  114. continue
  115.         datum["date"]= converter(datum_date)
  116. return data
  117. def cast_float(value):
  118. """
  119.     Return a floated value or none
  120.     """
  121. try:
  122. return float(value)
  123. except(ValueError,TypeError):
  124. returnNone
  125. def get_series(
  126.     indicator,
  127.     country="all",
  128.     data_date=None,
  129.     freq="Y",
  130.     source=None,
  131.     convert_date=False,
  132.     column_name="value",
  133.     keep_levels=False,
  134.     cache=True,
  135. ):
  136. """
  137.     Retrieve indicators for given countries and years
  138.     :indicator: the desired indicator code
  139.     :country: a country code, sequence of country codes, or "all" (default)
  140.     :data_date: the desired date as a datetime object or a 2-tuple with start
  141.         and end dates
  142.     :freq: the desired periodicity of the data, one of 'Y' (yearly), 'M'
  143.         (monthly), or 'Q' (quarterly). The indicator may or may not support the
  144.         specified frequency.
  145.     :source: the specific source to retrieve data from (defaults on API to 2,
  146.         World Development Indicators)
  147.     :convert_date: if True, convert date field to a datetime.datetime object.
  148.     :column_name: the desired name for the pandas column
  149.     :keep_levels: if True and pandas is True, don't reduce the number of index
  150.         levels returned if only getting one date or country
  151.     :cache: use the cache
  152.     :returns: WBSeries
  153.     """
  154.     raw_data = get_data(
  155.         indicator=indicator,
  156.         country=country,
  157.         data_date=data_date,
  158.         freq=freq,
  159.         source=source,
  160.         convert_date=convert_date,
  161.         cache=cache,
  162. )
  163.     df = pd.DataFrame(
  164. [[i["country"]["value"], i["date"], i["value"]]for i in raw_data],
  165.         columns=["country","date", column_name],
  166. )
  167.     df[column_name]= df[column_name].map(cast_float)
  168. ifnot keep_levels and len(df["country"].unique())==1:
  169.         df = df.set_index("date")
  170. elifnot keep_levels and len(df["date"].unique())==1:
  171.         df = df.set_index("country")
  172. else:
  173.         df = df.set_index(["country","date"])
  174.     series =WBSeries(df[column_name])
  175.     series.last_updated = raw_data.last_updated
  176. return series
  177. def data_date_to_str(data_date, freq):
  178. """
  179.     Convert data_date to the appropriate representation base on freq
  180.     :data_date: A datetime.datetime object to be formatted
  181.     :freq: One of 'Y' (year), 'M' (month) or 'Q' (quarter)
  182.     """
  183. if freq =="Y":
  184. return data_date.strftime("%Y")
  185. if freq =="M":
  186. return data_date.strftime("%YM%m")
  187. if freq =="Q":
  188. return f"{data_date.year}Q{(data_date.month - 1) // 3 + 1}"
  189. def get_data(
  190.     indicator,
  191.     country="all",
  192.     data_date=None,
  193.     freq="Y",
  194.     source=None,
  195.     convert_date=False,
  196.     pandas=False,
  197.     column_name="value",
  198.     keep_levels=False,
  199.     cache=True,
  200. ):
  201. """
  202.     Retrieve indicators for given countries and years
  203.     :indicator: the desired indicator code
  204.     :country: a country code, sequence of country codes, or "all" (default)
  205.     :data_date: the desired date as a datetime object or a 2-tuple with start
  206.         and end dates
  207.     :freq: the desired periodicity of the data, one of 'Y' (yearly), 'M'
  208.         (monthly), or 'Q' (quarterly). The indicator may or may not support the
  209.         specified frequency.
  210.     :source: the specific source to retrieve data from (defaults on API to 2,
  211.         World Development Indicators)
  212.     :convert_date: if True, convert date field to a datetime.datetime object.
  213.     :cache: use the cache
  214.     :returns: list of dictionaries
  215.     """
  216. if pandas:
  217.         warnings.warn(
  218. (
  219. "Argument 'pandas' is deprecated and will be removed in a "
  220. "future version. Use get_series or get_dataframe instead."
  221. ),
  222. PendingDeprecationWarning,
  223. )
  224. return get_series(
  225.             indicator=indicator,
  226.             country=country,
  227.             data_date=data_date,
  228.             source=source,
  229.             convert_date=convert_date,
  230.             column_name=column_name,
  231.             keep_levels=keep_levels,
  232.             cache=cache,
  233. )
  234.     query_url = COUNTRIES_URL
  235. try:
  236.         c_part = parse_value_or_iterable(country)
  237. exceptTypeError:
  238. raiseTypeError("'country' must be a string or iterable'")
  239.     query_url ="/".join((query_url, c_part,"indicators", indicator))
  240.     args ={}
  241. if data_date:
  242.         args["date"]=(
  243. ":".join(data_date_to_str(dd, freq)for dd in data_date)
  244. if isinstance(data_date, collections.Sequence)
  245. else data_date_to_str(data_date, freq)
  246. )
  247. if source:
  248.         args["source"]= source
  249.     data = fetch(query_url, args, cache=cache)
  250. if convert_date:
  251.         data = convert_dates_to_datetime(data)
  252. return data
  253. def id_only_query(query_url, query_id, cache):
  254. """
  255.     Retrieve information when ids are the only arguments
  256.     :query_url: the base url to use for the query
  257.     :query_id: an id or sequence of ids
  258.     :cache: use the cache
  259.     :returns: WBSearchResult containing dictionary objects describing results
  260.     """
  261. if query_id:
  262.         query_url ="/".join((query_url, parse_value_or_iterable(query_id)))
  263. returnWBSearchResult(fetch(query_url))
  264. def get_source(source_id=None, cache=True):
  265. """
  266.     Retrieve information on a source
  267.     :source_id: a source id or sequence thereof.  None returns all sources
  268.     :cache: use the cache
  269.     :returns: WBSearchResult containing dictionary objects describing selected
  270.         sources
  271.     """
  272. return id_only_query(SOURCES_URL, source_id, cache=cache)
  273. def get_incomelevel(level_id=None, cache=True):
  274. """
  275.     Retrieve information on an income level aggregate
  276.     :level_id: a level id or sequence thereof.  None returns all income level
  277.         aggregates
  278.     :cache: use the cache
  279.     :returns: WBSearchResult containing dictionary objects describing selected
  280.         income level aggregates
  281.     """
  282. return id_only_query(ILEVEL_URL, level_id, cache=cache)
  283. def get_topic(topic_id=None, cache=True):
  284. """
  285.     Retrieve information on a topic
  286.     :topic_id: a topic id or sequence thereof.  None returns all topics
  287.     :cache: use the cache
  288.     :returns: WBSearchResult containing dictionary objects describing selected
  289.         topic aggregates
  290.     """
  291. return id_only_query(TOPIC_URL, topic_id, cache=cache)
  292. def get_lendingtype(type_id=None, cache=True):
  293. """
  294.     Retrieve information on an income level aggregate
  295.     :level_id: lending type id or sequence thereof.  None returns all lending
  296.         type aggregates
  297.     :cache: use the cache
  298.     :returns: WBSearchResult containing dictionary objects describing selected
  299.         topic aggregates
  300.     """
  301. return id_only_query(LTYPE_URL, type_id, cache=cache)
  302. def get_country(country_id=None, incomelevel=None, lendingtype=None, cache=True):
  303. """
  304.     Retrieve information on a country or regional aggregate.  Can specify
  305.     either country_id, or the aggregates, but not both
  306.     :country_id: a country id or sequence thereof. None returns all countries
  307.         and aggregates.
  308.     :incomelevel: desired incomelevel id or ids.
  309.     :lendingtype: desired lendingtype id or ids.
  310.     :cache: use the cache
  311.     :returns: WBSearchResult containing dictionary objects representing each
  312.         country
  313.     """
  314. if country_id:
  315. if incomelevel or lendingtype:
  316. raiseValueError("Can't specify country_id and aggregates")
  317. return id_only_query(COUNTRIES_URL, country_id, cache=cache)
  318.     args ={}
  319. if incomelevel:
  320.         args["incomeLevel"]= parse_value_or_iterable(incomelevel)
  321. if lendingtype:
  322.         args["lendingType"]= parse_value_or_iterable(lendingtype)
  323. returnWBSearchResult(fetch(COUNTRIES_URL, args, cache=cache))
  324. def get_indicator(indicator=None, source=None, topic=None, cache=True):
  325. """
  326.     Retrieve information about an indicator or indicators.  Only one of
  327.     indicator, source, and topic can be specified.  Specifying none of the
  328.     three will return all indicators.
  329.     :indicator: an indicator code or sequence thereof
  330.     :source: a source id or sequence thereof
  331.     :topic: a topic id or sequence thereof
  332.     :cache: use the cache
  333.     :returns: WBSearchResult containing dictionary objects representing
  334.         indicators
  335.     """
  336. if indicator:
  337. if source or topic:
  338. raiseValueError(INDIC_ERROR)
  339.         query_url ="/".join((INDICATOR_URL, parse_value_or_iterable(indicator)))
  340. elif source:
  341. if topic:
  342. raiseValueError(INDIC_ERROR)
  343.         query_url ="/".join(
  344. (SOURCES_URL, parse_value_or_iterable(source),"indicators")
  345. )
  346. elif topic:
  347.         query_url ="/".join((TOPIC_URL, parse_value_or_iterable(topic),"indicators"))
  348. else:
  349.         query_url = INDICATOR_URL
  350. returnWBSearchResult(fetch(query_url, cache=cache))
  351. def search_indicators(query, source=None, topic=None, cache=True):
  352. """
  353.     Search indicators for a certain regular expression.  Only one of source or
  354.     topic can be specified. In interactive mode, will return None and print ids
  355.     and names unless suppress_printing is True.
  356.     :query: the term to match against indicator names
  357.     :source: if present, id of desired source
  358.     :topic: if present, id of desired topic
  359.     :cache: use the cache
  360.     :returns: WBSearchResult containing dictionary objects representing search
  361.         indicators
  362.     """
  363.     indicators = get_indicator(source=source, topic=topic, cache=cache)
  364.     pattern = re.compile(query, re.IGNORECASE)
  365. returnWBSearchResult(for i in indicators if pattern.search(i["name"]))
  366. def search_countries(query, incomelevel=None, lendingtype=None, cache=True):
  367. """
  368.     Search countries by name.  Very simple search.
  369.     :query: the string to match against country names
  370.     :incomelevel: if present, search only the matching incomelevel
  371.     :lendingtype: if present, search only the matching lendingtype
  372.     :cache: use the cache
  373.     :returns: WBSearchResult containing dictionary objects representing
  374.         countries
  375.     """
  376.     countries = get_country(
  377.         incomelevel=incomelevel, lendingtype=lendingtype, cache=cache
  378. )
  379.     pattern = re.compile(query, re.IGNORECASE)
  380. returnWBSearchResult(for i in countries if pattern.search(i["name"]))
  381. @uses_pandas
  382. def get_dataframe(
  383.     indicators,
  384.     country="all",
  385.     data_date=None,
  386.     freq="Y",
  387.     source=None,
  388.     convert_date=False,
  389.     keep_levels=False,
  390.     cache=True,
  391. ):
  392. """
  393.     Convenience function to download a set of indicators and  merge them into a
  394.         pandas DataFrame.  The index will be the same as if calls were made to
  395.         get_data separately.
  396.     :indicators: An dictionary where the keys are desired indicators and the
  397.         values are the desired column names
  398.     :country: a country code, sequence of country codes, or "all" (default)
  399.     :data_date: the desired date as a datetime object or a 2-sequence with
  400.         start and end dates
  401.     :freq: the desired periodicity of the data, one of 'Y' (yearly), 'M'
  402.         (monthly), or 'Q' (quarterly). The indicator may or may not support the
  403.         specified frequency.
  404.     :source: the specific source to retrieve data from (defaults on API to 2,
  405.         World Development Indicators)
  406.     :convert_date: if True, convert date field to a datetime.datetime object.
  407.     :keep_levels: if True don't reduce the number of index levels returned if
  408.         only getting one date or country
  409.     :cache: use the cache
  410.     :returns: a WBDataFrame
  411.     """
  412.     serieses =[
  413. (
  414.             get_series(
  415.                 indicator=indicator,
  416.                 country=country,
  417.                 data_date=data_date,
  418.                 freq=freq,
  419.                 source=source,
  420.                 convert_date=convert_date,
  421.                 keep_levels=keep_levels,
  422.                 cache=cache,
  423. ).rename(name)
  424. )
  425. for indicator, name in indicators.items()
  426. ]
  427.     result =None
  428. for series in serieses:
  429. if result isNone:
  430.             result = series.to_frame()
  431. else:
  432.             result = result.join(series.to_frame(), how="outer")
  433.     result =WBDataFrame(result)
  434.     result.last_updated ={i.name: i.last_updated for i in serieses}
  435. return result
  436. """
  437. wbdata.fetcher: retrieve and cache queries
  438. """
  439. import datetime
  440. import json
  441. import logging
  442. import pickle
  443. import pprint
  444. import os
  445. import appdirs
  446. import requests
  447. from pathlib importPath
  448. EXP =7
  449. PER_PAGE =1000
  450. TODAY = datetime.date.today()
  451. TRIES =5
  452. classWBResults(list):
  453. def __init__(self,*args,**kwargs):
  454.         super().__init__(*args,**kwargs)
  455.         self.last_updated =None
  456. classCache(object):
  457. """Docstring for Cache """
  458. def __init__(self):
  459.         self.path =Path(
  460.             os.getcwd()+'/cache/'+__version__
  461. )
  462.         self.path.parent.mkdir(parents=True, exist_ok=True)
  463. try:
  464. with self.path.open("rb")as cachefile:
  465.                 self.cache ={
  466.                     i:(date, json)
  467. for i,(date, json)in pickle.load(cachefile).items()
  468. if(TODAY - datetime.date.fromordinal(date)).days < EXP
  469. }
  470. except(IOError,EOFError):
  471.             self.cache ={}
  472. def __getitem__(self, key):
  473. return self.cache[key][1]
  474. def __setitem__(self, key, value):
  475.         self.cache[key]= TODAY.toordinal(), value
  476.         self.sync()
  477. def __contains__(self, item):
  478. return item in self.cache
  479. def sync(self):
  480. """Sync cache to disk"""
  481. with self.path.open("wb")as cachefile:
  482.             pickle.dump(self.cache, cachefile)
  483. CACHE =Cache()
  484. def get_json_from_url(url, args):
  485. """
  486.     Fetch a url directly from the World Bank, up to TRIES tries
  487.     :url: the  url to retrieve
  488.     :args: a dictionary of GET arguments
  489.     :returns: a string with the url contents
  490.     """
  491. for _ in range(TRIES):
  492. try:
  493. return requests.get(url, args).text
  494. except requests.ConnectionError:
  495. continue
  496.     logging.error(f"Error connecting to {url}")
  497. raiseRuntimeError("Couldn't connect to API")
  498. def get_response(url, args, cache=True):
  499. """
  500.     Get single page response from World Bank API or from cache
  501.     : query_url: the base url to be queried
  502.     : args: a dictionary of GET arguments
  503.     : cache: use the cache
  504.     : returns: a dictionary with the response from the API
  505.     """
  506.     logging.debug(f"fetching {url}")
  507.     key =(url, tuple(sorted(args.items())))
  508. if cache and key in CACHE:
  509.         response = CACHE[key]
  510. else:
  511.         response = get_json_from_url(url, args)
  512. if cache:
  513.             CACHE[key]= response
  514. return json.loads(response)
  515. def fetch(url, args=None, cache=True):
  516. """Fetch data from the World Bank API or from cache.
  517.     Given the base url, keep fetching results until there are no more pages.
  518.     : query_url: the base url to be queried
  519.     : args: a dictionary of GET arguments
  520.     : cache: use the cache
  521.     : returns: a list of dictionaries containing the response to the query
  522.     """
  523. if args isNone:
  524.         args ={}
  525. else:
  526.         args = dict(args)
  527.     args["format"]="json"
  528.     args["per_page"]= PER_PAGE
  529.     results =[]
  530.     pages, this_page =0,1
  531. while pages != this_page:
  532.         response = get_response(url, args, cache=cache)
  533. try:
  534.             results.extend(response[1])
  535.             this_page = response[0]["page"]
  536.             pages = response[0]["pages"]
  537. except(IndexError,KeyError):
  538. try:
  539.                 message = response[0]["message"][0]
  540. raiseRuntimeError(
  541.                     f"Got error {message['id']} ({message['key']}): "
  542.                     f"{message['value']}"
  543. )
  544. except(IndexError,KeyError):
  545. raiseRuntimeError(
  546.                     f"Got unexpected response:\n{pprint.pformat(response)}"
  547. )
  548.         logging.debug(f"Processed page {this_page} of {pages}")
  549.         args["page"]= int(this_page)+1
  550. for i in results:
  551. if"id"in i:
  552.             i["id"]= i["id"].strip()
  553.     results =WBResults(results)
  554. try:
  555.         results.last_updated = datetime.datetime.strptime(
  556.             response[0]["lastupdated"],"%Y-%m-%d"
  557. )
  558. exceptKeyError:
  559. pass
  560. return results

 

用法:

  1.     
  2.  
  3. if __name__  == '__main__':
  4.  
  5.     print('========================== start worldbank =====================')
  6.     #topic-> indicator
  7.  
  8.     print(os.getcwd())
  9.  
  10.     countries = ['CHN', 'USA']
  11.     indicators = {'NY.GDP.PCAP.PP.CD' : 'GDP per capita, PPP (current international $)'}
  12.     dt = (datetime.datetime(2020, 1, 1), datetime.datetime(2022, 1, 1))
  13.     #df = WorldBankAPI.get_dataframe(indicators, country=countries, convert_date=False, data_date=dt)
  14.     df = WorldBankAPI.get_dataframe(indicators, convert_date=False, data_date=dt)
  15.  
  16.     print(df)
  17.  
  18.     
  19.  
  20.     print('==========================  end ============================')

 

============ 欢迎各位老板打赏~ ===========

本文版权归Bruce's Blog所有,转载引用请完整注明以下信息:
本文作者:Bruce
本文地址:可在阿里云dataworks直接使用的worldbank data api | Bruce's Blog

发表评论

留言无头像?